C# 知识点补漏(持续更新)
将以前的一些笔记整理了一下,方便查询知识点
2020-12-23
- ?用法:
int ? a=null;表示定义的类型可以为null, 通过a.Value来获取值,它对应的类型是NullAble<T>
看下面一张图,假如terminalConfiguration为null,它会跳转到if语句中Object b = c??d;表示假c==null,返回d,否则返回c,该运算符从右往左运算,即a??b??c为a??(b??c)来计算
- 字符串拼接
不推荐使用+,使用string.Format或者$"{param1} {param2} is ..",后两种的底层实现是一样的,效率会更高
+和string.concat的底层原理是一致的 - AppDomain
AppDomain是CLR的运行单元,它可以加载Assembly、创建对象以及执行程序。
AppDomain是CLR实现代码隔离的基本机制。AppDomain是个静态概念,只是限定了对象的边界
AppDomain被创建在进程中,一个进程内可以有多个AppDomain。一个AppDomain只能属于一个进程 - IReadOnlyCollection阻止调用方转换为其他集合类型。 对于这种保护,必须使用类ReadOnlyCollection<T>
- 一种优雅的转换方式
string a = "123"; if(a is object b){ //then we can use b as objcet }
- 获取变量或者类名推荐使用“nameof()”
C# 6.0 引入了一个名为“nameof”的新的操作符,它的作用是接收元素而后返回元素名字。这个操作符能将class和class的所用成员,比如方法、变量以及属性作为参数而后返回一个它们的名字。这避免我们在代码中hardcode字符串,也避免使用反射来获得这些名字 -
Marshal.AllocHGlobal 方法 (Int32)
通过使用指定的字节数,从进程的非托管内存中分配内存IntPtr hglobal = Marshal.AllocHGlobal(100); Marshal.FreeHGlobal(hglobal);
- 查看.sln看项目版本
+---------------------------+---------------+-----------+----------------+ | Product name | Codename | Version # | .NET Framework | +---------------------------+---------------+-----------+----------------+ | Visual Studio 4.0 | N/A | 4.0.* | N/A | | Visual Studio 97 | Boston | 5.0.* | N/A | | Visual Studio 6.0 | Aspen | 6.0.* | N/A | | Visual Studio .NET (2002) | Rainier | 7.0.* | 1 | | Visual Studio .NET 2003 | Everett | 7.1.* | 1.1 | | Visual Studio 2005 | Whidbey | 8.0.* | 2.0, 3.0 | | Visual Studio 2008 | Orcas | 9.0.* | 2.0, 3.0, 3.5 | | Visual Studio 2010 | Dev10/Rosario | 10.0.* | 2.0 – 4.0 | | Visual Studio 2012 | Dev11 | 11.0.* | 2.0 – 4.5.2 | | Visual Studio 2013 | Dev12 | 12.0.* | 2.0 – 4.5.2 | | Visual Studio 2015 | Dev14 | 14.0.* | 2.0 – 4.6 | +---------------------------+---------------+-----------+----------------+ VS 2017 对应 15 Vs 2019 对应 16
- Resize<T>(ref T[] array, int newSize)
返回一个新的数组,长度是newSize,并把原数组里面的对应长度的值拷贝到新的数组 - yield 关键字
其实是一种语法糖,最终还是通过实现IEnumberable<T>、IEnumberable、IEnumberator<T>和IEnumberator接口实现的迭代功能。
yield return实现了类似用foreach遍历数组的功能
yield break是用来终止迭代的
假如使用yield return 返回的是一个IEnumerable 类型的值 - 浅拷贝和深拷贝
浅拷贝的定义 —— 只对值类型(或string)类型分配新的内存地址。
深拷贝的定义 —— 对值类型分配新的内存地址,引用类型、以及引用类型的内部字段分配的新的地址。
- winform中的NotifyIcon控件
NotifyIcon是一个比较特殊的组件,其特殊之处是既可以把它归类到控件中,也可以把它归类到组件中。这是因为将其拖放到设计窗体后,我们并不能马上看到它的界面(像组件),而是在运行时才能看到它(像控件)通知区域中的图标是一些进程的快捷方式,这些进程在计算机后台运行,如防病毒程序或音量控制。这些进程不会具有自己的用户界面。NotifyIcon 类提供了编写此功能的方法。Icon 属性定义显示在通知区域中的图标。图标的弹出菜单由 ContextMenu 属性确定。Text 属性分配工具提示文本。要在通知区域中显示图标,必须将 Visible 属性设置为 true - using()方式的使用
必须确保代码再using的逻辑里面使用,比如memoryStream等流的使用或者数据库,串口的使用,以EFCore为例
- IPropertyChanged事件使用方法
-
IEnumerable, ICollection, ObservableCollection
IEnumerable只有一个GetEnumerator方法ICollection继承自IEnumerable,多了增加删除,count等方法
ObservableCollection多了CollectionChanged和PropertyChanged事件,常用于UI
- 元组的使用方法,当dictionary或者list不够用的时候,推荐使用ValueTuple
(int intValue, string stringValue, string StringName) name = (intValue: 1, stringValue:"123", StringName: "33"); var str = name.stringValue; var str2 = name.StringName; var number = name.intValue;
- Tuple和ValueTuple
Tuple是引用类型,ValueTuple是值类型,所以推荐用后者 - 当我们定义接口中的属性时,对于容器一般定义为最顶层的IEnumerable, 那么假如他的实现类的子类需要用到如何办
public class Test : ITest { // 公开接口 public IEnumerable<string> Items => _Items; // 私有成员 private readonly ObservableCollection<string> _Items = new ObservableCollection<string>(); // 子类操作成员 protected ObservableCollection<string> MenuItems => _Items; }
- 通常我们定义model都是定义的Immutable,意思是所有成员都是readonly的,只有在构造的时候传入值
最典型的例子是string,任何操作都会重新创建一个新的string对象
好处:便于多线程编程,便于作为hashTable的key,便于比较状态
Immutable离不开2个关键字:const和readOnly
read only允许在构造的时候改变它的值,但是const不允许,只支持在定义的时候赋值 - 两种死循环的表达方式 while (true) , for (;;)
- enum的一些用法
- 获取登录用户名
string username = System.Environment.UserName;
- TaskAwaiter的使用
public class TestTask : IDisposable { private readonly Command _command; private readonly CancellationTokenSource _cancellationTokenSource; private readonly Task _executingTask; public TestTask(Command command) { _command = command; _cancellationTokenSource = new CancellationTokenSource(); _executingTask = _command.ExecuteAsync(_cancellationTokenSource.Token); } public TaskAwaiter GetAwaiter() { return _executingTask.GetAwaiter(); } public void Dispose() { CancelInternal(); if (_executingTask.Status == TaskStatus.Running) { _executingTask.Wait(); } _cancellationTokenSource.Dispose(); _executingTask.Dispose(); } private void CancelInternal() { if (_cancellationTokenSource.IsCancellationRequested) { return; } _cancellationTokenSource.Cancel(); } } class Program { static void Main(string[] args) { var task = new TestTask(command); await task; } }
- Expression和Lambda的区别
看下面的图片,Expression是一个type,把Lambda包装成了一个变量
- Lazy<T>的用法
Lazy表示只有在第一次使用的时候才会去创建对象
如下图,第一次为false,第二次为true
在unitTest里面使用Substitute
- 不要使用magic value
- 对IEnumerable<T>的处理方式
- 一种设计模式的理解
IServiceResultFactory<TErrorCode>是总的接口,然后ServiceResultFactory<TErrorCode>是其实现类,CommandCreationResultFactory是ServiceResultFactory的包含特定TErrorCode的子类,那么为什么要多一个ICommandCreationResultFactory来继承自ServiceResultFactory<TErrorCode>.
很显然,他是CommandCreationResultFactory的接口,它只做一件事,包含特定的TErrorCode,那么外部使用ICommandCreationResultFactory的时候,TErrorCode已经包含,并且因为CommandCreationResultFactory继承自ServiceResultFactory,它包含了所有的IServiceResultFactory<TErrorCode>里面的方法实现。
- c#内存
共有5个内存区域,堆,栈,常量区,静态区,方法区 - 一个问题
- 一个例子
public class Test { public static void main(String[] args) { Child demo = new Child(); demo.function(); Console.WriteLine("…………………………………………………………………………………………………………………………"); Child child = new Child(); child.function(); } } 启动流程: 1、 启动CLR,开始分配内存空间; 2、 开始加载Test.class文件,加载到方法区中,在加载的过程中静态的内容要进入静态区中; 3、 在开始运行main方法,这时CLR就会把main调用到栈中运行,开始从方法的第一行往下运行; 4、 在main方法中new Child();这时CLR就会在方法区中查找有没有Child文件,如果没有就加载Child.class文件,并且Child继承Parent类,所以也要查找有没有Parent类,如果没有也要加载Parent.class文件。 5、 Child.class和Parent.class中的所有的非静态内容会加载到非静态的区域中,而静态的内容会加载到静态区中。静态内容(静态变量,静态方法)按照书写顺序加载。 说明:类的加载只会执行一次。下次再创建对象时,可以直接在方法区中获取class信息。 6、 开始给静态区中的所有静态的成员变量开始默认初始化。默认初始化完成之后,开始给所有的静态成员变量显示初始化。 7、 所有静态成员变量显示初始化完成之后,开始调用构造函数 ①有个隐式的super(); ②显示初始化(给所有的非静态的成员变量) ③执行构造代码块 之后才开始执行本类的构造方法中的代码 super()是调用父类的构造函数,此处即为Parent的构造函数,在Parent的构造函数中也有个隐式三步:首先super(),再执行Parent的显示初始化,然后执行Parent的构造函数中的代码。 Parent的执行完之后,回来继续执行Child自己的隐式三步中的第二步:显示初始化,然后执行Child的非静态代码块的,最后执行Child的构造函数中的代码 8、对象创建完成,把内存的地址赋值给demo使用。 9、执行demo.function()方法。 10、由于后面又创建(new)了一个新的Child对象,因此重复一下【7】之后的步骤
- 一些概念
AppDomain: 程序启动得时候会默认创建一个appDomain域
Application:Encapsulates a Windows Presentation Foundation application. Windows应用程序,WPF和winform都是继承自它
Window:每一个xaml就是一个window,继承自它。 - ref和out关键字表示都是传的地址
- 分割字符串
Regex分割
string[] sArray=Regex.Split(str,"js",RegexOptions.IgnoreCase);
Split分割
// (以下string words = "1,2.3,,4";): string[] split = words.Split(new string[] { ",", "." }, 2, StringSplitOptions.RemoveEmptyEntries);//返回:{"1","2.3,,4"} 不保留空元素 string[] split = words.Split(new string[] { ",", "." }, 6, StringSplitOptions.None);//返回:{"1","2","3","","4"} 保留空元素 string[] split = words.Split(new Char[] { ',', '.' }, 2, StringSplitOptions.RemoveEmptyEntries);//返回:{"1","2.3,,4"} 不保留空元素 string[] split = words.Split(new Char[] { ',', '.' }, 6, StringSplitOptions.None);//返回:{"1","2","3","","4"} 保留空元素
2021-5-7
Struct 和Class区别
简单来说,struct是值类型,创建一个struct类型的实例被分配在栈上。class是引用类型,创建一个class类型实例被分配在托管堆上。但struct和class的区别远不止这么简单。
概括来讲,struct和class的不同体现在:
● 类是引用类型,struct是值类型
● 在托管堆上创建类的实例,在栈上创建struct实例
● 类实例的赋值,赋的是引用地址,struct实例的赋值,赋的是值
● 类作为参数类型传递,传递的是引用地址,struct作为参数类型传递,传递的是值
● 类没有默认无参构造函数,struct有默认无参构造函数
● 类支持继承,struct不支持继承
● 类偏向于"面向对象",用于复杂、大型数据,struct偏向于"简单值",比如小于16字节,结构简单
● 类的成员很容易赋初值,很难给struct类型成员赋初值
● 类的实例只能通过new SomeClass()来创建,struct类型的实例既可以通过new SomeStruct()来创建,也可以通过SomeStruct myStruct;来创建
2021-7-6
使用IDisposable
public class xxx : IDisposable { private bool _disposed; /// <summary> /// dispose method /// </summary> /// <param name="disposing">disposing</param> protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // dispose your objects } _disposed = true; } /// <inheritdoc/> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }
2021/8/30
通过linq语句判断2个复杂对象是否相同
public static bool AreDataEqual(this PairingEntryDto pairingEntryDto, PairingEntryDto other) { var result = pairingEntryDto.Id == other.Id && pairingEntryDto.LogTime == other.LogTime && pairingEntryDto.TerminalSerialNumber == other.TerminalSerialNumber && pairingEntryDto.ScalePairingHistory.Count == other.ScalePairingHistory.Count; if (!result) return false; return !pairingEntryDto.ScalePairingHistory.Where((t, i) => t.Status != other.ScalePairingHistory[i].Status || t.PairingInformation != other.ScalePairingHistory[i].PairingInformation).Any(); }
2021/9/10
public static Delegate? Combine (params Delegate?[]? delegates);的用法
Delegate.Combine Method (System) | Microsoft Docs
将多个delegate连接,invoke的时候按顺序执行,参数可为null
Action aa = null; Action bb = delegate { Console.WriteLine("B"); }; Action cc = (Action)Delegate.Combine(aa, bb); cc();
上图执行结果为:
2021/9/13
函数返回2个结果,设置成元组
public (bool oldState, bool newState) UpdateInitializedState(bool IsInitialized)
{
var oldState = IsInitialized;
var newState = IsInitialized;
return (oldState, newState);
}