如果在匿名函数内部捕获到外部的变量,
被捕获到的是变量,而不是创建委托实例时该变量的值。在匿名方法外对变量的更改在匿名方法内部是可见的,反之亦然。
如果只是创建一个委托实例,不会读取变量并将它的值存储到某个地方。 作用:简单来说,捕获变量能
简化避免专门创建一些类来储存一个委托需要处理的信息(除了作为参数传递的信息之外)。
内存:如果一个值类型在匿名函数被捕获,那么它就不在栈上了。编译器创建了一个额外的类来容纳变量,CreateDelegateInstance方法拥有对该类的一个实例的引用,所以它能使用外部的变量。另外,委托也会创建一个实例方法作为委托的操作。
由于非常复杂的捕获嵌套,如果用或不用捕获会更加简单,那就不要用比较好。
ArrayList, List<T>List是泛型,编译器会检查类型,并且没有装箱操作。ArrayList则有装箱操作,它会把加入的值类型转化为引用类型。
ArrayList中的实例,有可能需要
强制类型转换的操作,这事实上事告诉编译器“
我比你知道的多一点”,但这又意味着你可能是错误的。
三种对列表进行排序的方法:
委托,lambda表达式和扩展方法:
// 使用委托进行排序 而不必重写IComparer方法
List<Product> products = Product.GetSampleProducts();
products.Sort(delegate(Product x, Product y)
{return x.Name.CompareTo(y.Name)}
);
// 使用lambda表达式进行排序
List<Product> products = Product.GetSampleProducts();
products.Sort((x, y) => x.Name.CompareTo(y.Name));
// 使用扩展方法
foreach (Product product in products.OrderBy(p=>p.Name))
{
Console.WriteLine(product);
}
扩展方法并不是List的一个方法,这里也不是对List进行排序,而是按照特定的顺序获取列表的内容 。有时,需要更改实际的列表,有时则不需要。
// 使用委托
Predicate<Product> test = delegate(Product p){return p.Price > 10m;};
List<Product> matches = products.FindAll(test);
Action<Product> pritn = Console.WriteLine;
matches.ForEach(print);
// 一行
products.FindAll(delegate(Product p){return p.Price > 10;}).ForEach(Console.WriteLine);
// lamda表达式
foreach(Product product in products.Where(p => p.Price >10))
{
Console.WriteLine(product);
}
值类型和引用类型的误区
- 结构体是"轻量级"的类
结构体是值类型。
例如Vector和DeltaTime等, 它们作为值类型是很有道理的,因为它们非常适合数字或字符的一个基本单位来使用。另外,它也理应被赋予对它的值执行计算的能力。
值类型优势:不需要被垃圾回收,不会因类型标识而产生开销,也不需要解引用。
引用类型优势:传递参数,赋值,将值返回,只需复制指针(4或8字节),而不是复制全部数据。
2.
引用保存在堆上(正确),值类型保存在栈上(错误)。
前半句正确,后半句错误。
假定一个类中有一个int类型实例变量,那么在这个类的任何对象中,该变量的值和对象的其它数据在一起,也就是在栈上。
只有局部变量和方法参数在栈上。
3.
对象在C#默认是通过引用传递的说这句话的人知道C#实际的行为是什么,但不知道引用传递(pass by reference)的真正意思是什么。引用类型变量的值是引用,而不是对象本身。不需要按照引用来传递参数本身,就可以更改该参数引用的那个对象的内容。
void AppendHello(StringBuilder builder)
{
builder.Append("Hello")
}
这个例子中,参数值(对StringBuilder的一个引用)是以值传递方式进行传递的。如果想在方法内部更改builder的变量的值,只是更改的builder的一个副本。无论是值传递还是引用传递,
永远不会传递对象本身。
4.
装箱和拆箱为一个值调用ToString, Equals, GetHashCode()等方法,如果该类型没有覆盖这些方法,也会发生装箱。当调用值类型的GetType()方法总会伴随着装箱过程,因为它不能被重载。s使用TypeOf不会有这个问题。
泛型相关
为什么需要泛型主要是因为强制类型转换非常糟糕,有问题就无法通过编译,这样就非常安全。而试图证明自己比编译器知道的更多,往往是比较危险的。
各种特性 TODO
同C++模板相比
- 对于普通C++来说,会为一套特定的模版编译代码,好像模版实参本来就在源代码中一样。编译是一次完成的,而不是像C#一样, 先编译成IL, 再由JIT编译成本地代码 。一个C++程序以十种不同的方式使用一个标准模版,就会在程序中 包含代码十次 。而在C#中,根本就不会包含泛型代码,它只是引用一下泛型。执行时,需要多少个不同的版本, JIT就会编译多少个 。
- C++的模版比C#做的好的之一, 实参不要求必须是类型名,变量名,函数名,常量表达式也是允许的 。例如缓冲区类型,buffer<int, 20>是包含20个int值的缓冲区,它将缓冲区大小作为实参之一。这个功能对于 模板元编程 (metaprogramming)是至关重要的。
- C++模版在其他方面更加灵活。C#除了最简单的算数运算,其他运算都是显示使用Math类。C#还缺乏C风格的typedef(使用typedef定义了一个数据表示之后,就可以在一个程序的各个地方使用它,并可以轻松更改。)而C++没有这个限制。
空类型相关
为什么值类型不能为null?
值类型的变量的值是它本身的数据。一个非空引用值提供了访问一个对象的途径,然而null相当于一个特殊的值,它意味着我不用引用任何对象。
值类型变量直接在Stack中保存了数据,因此在生命周期结束前数据不能被任何形式的销毁,而引用类型变量在Heap中保存数据,所以赋值null其实是将对应在Heap中的数据销毁而不是结束变量的生命周期。
C#表示空值的四种方式:
- 魔值 牺牲一个值用来表示空,例如:(DateTime.MinValue)
- 引用类型包装, 使用Obj作为变量类型或定义一个新的类型(不展开讲了),但是会导致GC Alloc
- 额外的布尔标志. 使用一个普通值类型的值,用另外一个值表示该类型是真正存在的。
- System.Nullable<T>和 System.Nullable
System.Nullable<T>和 System.Nullable
Nullable<T>是一个结构,
一个值类型。具体不介绍了。
Yield用法
yield是一个语法糖,官方文档里的解释是:
如果你在语句中使用 yield 上下文关键字,则意味着它在其中出现的方法、运算符或 get 访问器是迭代器。通过使用 yield 定义迭代器,可在实现自定义集合类型的 IEnumerator<T> 和 IEnumerable 模式时无需其他显式类(保留枚举状态的类,有关示例,请参阅 IEnumerator)。
- yield return 一次返回一个元素
- yield break 中止迭代
迭代器方法和 get 访问器
迭代器的声明必须满足以下要求:
- 返回类型必须为 IEnumerable、IEnumerable<T>、IEnumerator 或 IEnumerator<T>。
- 声明不能有任何 in、ref 或 out 参数。
返回
yield
或 IEnumerable 的迭代器的 IEnumerator 类型为
object
。 如果迭代器返回 IEnumerable<T> 或 IEnumerator<T>,则必须将
yield return
语句中的表达式类型隐式转换为泛型类型参数。
以下情形中不能包含
yield return
或
yield break
语句:
- Lambda 表达式和匿名方法。
- 包含不安全的块的方法。 有关详细信息,请参阅 unsafe。
异常处理
不能将
yield return
语句置于 try-catch 块中。 可将
yield return
语句置于 try-finally 语句的 try 块中。
可将
yield break
语句置于 try 块或 catch 块中,但不能将其置于 finally 块中。
如果
foreach
主体(在迭代器方法之外)引发异常,则将执行迭代器方法中的
finally
块。
技术实现
以下代码从迭代器方法返回
IEnumerable<string>
,然后遍历其元素。
C#复制
IEnumerable<string> elements = MyIteratorMethod();
foreach (string element in elements)
{
...
}
调用
MyIteratorMethod
并不执行该方法的主体。 相反,该调用会将
IEnumerable<string>
返回到
elements
变量中。
在
foreach
循环迭代时,将为 MoveNext 调用
elements
方法。 此调用将执行
MyIteratorMethod
的主体,直至到达下一个
yield return
语句。
yield return
语句返回的表达式不仅决定了循环体使用的
element
变量值,还决定了
elements
的 Current 属性(它是
IEnumerable<string>
)。
在
foreach
循环的每个后续迭代中,迭代器主体的执行将从它暂停的位置继续,直至到达
yield return
语句后才会停止。 在到达迭代器方法的结尾或
foreach
语句时,
yield break
循环便已完成
注意:尽管使用yield语句,看起来像是顺序执行的方法,但实际上本质是一个状态机。调用者每次只想获取一个元素,因此返回上一个值时需要保留当前的工作状态。