天天看点

c# ienumerable 赋值_C#基础知识整理

匿名方法捕获变量(闭包) 要注意的:

如果在匿名函数内部捕获到外部的变量,

被捕获到的是变量

,而不是创建委托实例时该变量的值。在匿名方法外对变量的更改在匿名方法内部是可见的,反之亦然。

如果只是创建一个委托实例,不会读取变量并将它的值存储到某个地方。 作用:

简单来说,捕获变量能

简化避免专门创建一些类来储存一个委托需要处理的信息

(除了作为参数传递的信息之外)。

内存:

如果一个值类型在匿名函数被捕获,那么它就不在栈上了。编译器创建了一个额外的类来容纳变量,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);
}
           

值类型和引用类型的误区

  1. 结构体是"轻量级"的类

结构体是值类型。

例如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语句,看起来像是顺序执行的方法,但实际上本质是一个状态机。调用者每次只想获取一个元素,因此返回上一个值时需要保留当前的工作状态。

继续阅读