C#的变迁史02 - C# 2.0篇

  在此重申一下,本文仅代表个人观点,如有不妥之处,还请自己辨别。

  第一代的值类型装箱与拆箱的效率极其低下,特别是在集合中的表现,所以第二代C#重点解决了装箱的问题,加入了泛型。
1. 泛型 - 珍惜生命,远离装箱

  集合作为通用的容器,为了兼容各种类型,不得已使用根类Object作为成员类型,这在C#1.0中带来了很大的装箱拆箱问题。为了C#光明的前途,这个问题必须要解决,而且要解决好。

  C++模板是一个有用的启迪,虽然C++模板的运行机制不一样,但是思路确实是正确的。

  带有形参的类型,也就是C#中的泛型,作为一种方案,解决了装箱拆箱,类型安全,重用集合的功能,防止具有相似功能的类泛滥等问题。泛型最大的战场就是在集合中,以List<T>,Queue<T>,Stack<T>等泛型版本的集合基本取代了第一代中非泛型版本集合的使用场合。当然除了在集合中,泛型在其他的地方也有广泛的用途,因为程序员都是懒的,重用和应对变化是计算机编程技术向前发展最根本的动力。

  为了达到类型安全(比如调用的方法要存在),就必须有约定。本着早发现,早解决的思路,在编译阶段能发现的问题最好还是在编译阶段就发现,所以泛型就有了约束条件。泛型的约束常见的是下面几种:

a. 构造函数约束(使用new关键字),这个约束要求实例化泛型参数的时候要求传入的类必须有公开的无参构造函数。

b. 值类型约束(使用struct关键字),这个约束要求实例化泛型参数的类型必须是值类型。

c. 引用类型约束(使用class关键字),这个约束要求实例化泛型参数的类型必须是引用类型。

d. 继承关系约束(使用具体制定的类或接口),这个约束要求实例化泛型参数的类型必须是指定类型或是其子类。

  当然了,泛型参数的约束是可以同时存在多个的,参看下面的例子:

复制代码

public class Employee
{}
class MyList<T, V> where T: Employee, IComparable<T>where V: new()
{
}

复制代码

  如果不指定约束条件,那么默认的约束条件是Object,这个就不多讲了。

  当使用泛型方法的时候,需要注意,在同一个对象中,泛型版本与非泛型版本的方法如果编译时能明确关联到不同的定义是构成重载的。例如:

复制代码

public void Function1<T>(T a);
public void Function1<U>(U a);
这样是不能构成泛型方法的重载。因为编译器无法确定泛型类型T和U是否不同,也就无法确定这两个方法是否不同public void Function1<T>(int x);
public void Function1(int x);
这样可以构成重载public void Function1<T>(T t) where T:A;
public void Function1<T>(T t) where T:B;
这样不能构成泛型方法的重载。因为编译器无法确定约束条件中的A和B是否不同,也就无法确定这两个方法是否不同

复制代码

  使用泛型就简单了,直接把类型塞给形参,然后当普通的类型使用就可以了。例如:

List<int> ages = new List<int>();
ages.Add(0);
ages.Add(1);
ages.Remove(1);

2. 匿名函数delegate

  在C# 2.0中,终于实例化一个delegate不再需要使用通用的new方式了。使用delegate关键字就可以直接去实例化一个delegate。这种没有名字的函数就是匿名函数。这个不知道是不是语法糖的玩意儿使用起来确实比先定义一个函数,然后new实例的方式要方便。不过最方便的使用方式将在下一版中将会到来。

复制代码

delegate void TestDelegate(string s);
static void M(string s)
{Console.WriteLine(s);
}//C# 1.0的方式
TestDelegate testDelA = new TestDelegate(M);//C# 2.0 匿名方法
TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };

复制代码

  谈到匿名函数,不得不说说闭包的概念。

  如果把函数的工作范围比作一个监狱的话,函数内定义的变量就都是监狱中的囚犯,它们只能在这个范围内工作。一旦方法调用结束了,CLR就要回收线程堆栈空间,恢复函数调用前的现场;这些在函数中定义的变量就全部被销毁或者待销毁。但是有一种情况是不一样的,那就是某个变量的工作范围被人为的延长了,通俗的讲就像是某囚犯越狱了,它的工作范围超过了划定的监狱范围,这个时候它的生命周期就延长了。

  闭包就是使用函数作为手段延长外层函数中定义的变量的作用域和生命周期的现象,作为手段的这个函数就是闭包函数。看一个例子:

复制代码

class Program{static void Main(string[] args){List<Action> actions = getActions();foreach (var item in actions){item.Invoke();}}static List<Action> getActions(){List<Action> actions = new List<Action>();for (int i = 0; i < 5; i++){Action item = delegate() { Console.WriteLine(i); };actions.Add(item);}return actions;}}

复制代码

  你可以试试运行这个例子,结果和你预想的一致吗?这个例子会输出5个5,而不是0到4,出现这个现象的原因就是闭包。getActions函数中的变量i被匿名函数引用了,它在getActions调用结束后还会一直存活到匿名函数执行结束。但是匿名函数是后面才调用的,执行它们的时候,i早就循环完毕,值是5,所以最终所有的匿名函数执行结果都是输出5,这是由闭包现象导致的一个bug。

  要想修复这个由闭包导致的问题,方法基本上是破坏闭包引用,方式多种多样,下面是简单的利用值类型的深拷贝实现目的。

第一个方法:让闭包引用不再指向同一个变量

复制代码

for (int i = 0; i < 5; i++)
{int j = i;Action item = delegate() { Console.WriteLine(j); };actions.Add(item);
}

复制代码

第二个方法:包上一层函数来构造新的作用域

复制代码

static List<Action> getActions()
{List<Action> actions = new List<Action>();for (int i = 0; i < 5; i++){Action item = ActionMethod(i);actions.Add(item);}return actions;
}static Action ActionMethod(int p)
{return delegate(){Console.WriteLine(p);};
}

复制代码

  闭包现象提醒我们使用匿名函数和3.0中的Lambda表达式时都要时刻注意变量的来源。

3. 迭代器

  在C# 1.0中,集合实现迭代器模式是需要实现IEnumerable的,这个大家还记得吧,这个接口的核心就是GetEnumerator方法。实现这个接口主要是为了得到Enumerator对象,然后通过其提供的方法遍历集合(主要是Current属性和MoveNext方法)。自己去实现这些还是比较麻烦的,先需要定义一个Enumerator对象,然后在自定义的集合对象中还需要实现IEnumerable接口返回定义的Enumerator对象,于是一个新的语法糖就出现了: yield关键字。

  在C# 2.0中,只需要在自定义的集合对象中还需要实现IEnumerable接口返回一个Enumerator对象就行了,这个创建Enumerator对象的工作就由编译器自己完成了。看一个简单的小例子:

复制代码

public class Stack<T>:IEnumerable<T>
{T[] items;int count;public void Push(T data){...}public T Pop(){...}public IEnumerator<T> GetEnumerator(){for(int i=count-1;i>=0;--i){yield return items[i];}}
}

复制代码

  使用yield return创建一个Enumerator对象是不是很方便?编译器遇到yield return会创建一个Enumerator对象并自动维护这个对象。

  当然了,多数时候foreach必要遍历集合中的每一个元素,这个时候使用yield return配合for循环枚举每个元素就可以了,但是有时候只需要返回满足条件的部分元素,这个时候就要结合yield break中断枚举了,看一下:

复制代码

//使用yield break中断迭代:
for(int i=count-1;i>=0;--i)
{yield return items[i];if(items[i]>10){yield break;}
}

复制代码

4. 可空类型

  这个特性我觉得又是把值类型设计成引用类型Object类子类后,微软生产的怪语法。空值是引用类型的默认值,0值是值类型的默认值。那么在某些场合,比如从数据库中的记录取到内存中以后,没有值代表的是空值,但是字段的类型却是值类型,怎么搞呢?于是整出了可空类型。当然了,这个问题可以通过在设计表的时候给字段设计一个默认值来解决,但是有的时候某些字段的设置默认值是没有意义的,比如年龄,0有意义吗?

  可空类型的概念很简单,没什么可说的,不过一个相似的语法却让我感到很舒服:那就是"??"操作符。这是一个二元操作符,如果第一个操作数是空值,则执行第二个操作数代表的操作,并返回其结果。例如:

复制代码

static void Main(string[] args)
{int? age = null;age = age ?? 10;Console.WriteLine(age);
}

复制代码

5. 部分类与部分方法

  这一特性还是比较好用的,终于不用把所有的内容挤到一起了,终于可以申明和实现相分离了,虽然好像以前也可以做到,但是现在这项权利也下放给人民群众了。partial关键字带来了这一切,也带来了一定的扩展性。这个特性也比较简单,就是使用partial关键字。编译的时候,这些文件中定义的部分类会被合并。部分方法是3.0的特性,不过没什么新意,就放到2.0一起说吧。

  使用这个特性的时候需要注意:

a. 部分方法只能在部分类中定义。

b. 部分类和部分方法的签名应该是一致的。

c. partial用在类型上的时候只能出现在紧靠关键字 class、struct 或 interface 前面的位置。

d. public等访问修饰符必须出现在partial前面。

f. partial定义的东西必须是在同一程序集和模块中。

  看一个简单的例子:

复制代码

// File1.cs
namespace PC
{partial class A{int num = 0;void MethodA() { }partial void MethodC();}
}// File2.cs
namespace PC
{partial class A{void MethodB() { }partial void MethodC() { }}
}

复制代码

  这里需要注意一下MethodC的申明和定义是分开的就可以了。

  还有一点,很多人认为partial破坏了类的封装性,实际上谈不上。因为一个类能分部,就说明类的设计者认为是需要保留这个扩展性的,所以后面的人才可以给这个类添加一些新的东西。

6. 静态类

  这个特性也是比较符合实际情况的,很多情况下,某些对象只需要实例化一次,然后到处使用,单件模式是可以实现这个目的,现在静态类也是一个新的选择。

  静态类只能含有静态成员,所以构造函数也是静态的,既然是静态的,那么它与继承就没什么关系了。静态类从首次调用的时候创建,一直到程序结束时销毁。

  简单看一个小例子:

复制代码

public static class A
{static string message = "Message";static A(){Console.WriteLine("Initialize!");}public static void M(){Console.WriteLine(message);}
}

复制代码

  不过据经验讲,有没有静态构造函数对静态类的构造时间是有影响的,一个是出现在首次使用对象成员的时候,一个是程序集加载的时候,不过分清这个实在没什么意义,有兴趣的同学自己研究吧。

  C#2.0的新特性绝不止这几个,但是对程序猿们影响比较大的都在这了,更多的就参看微软的MSDN吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438140.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

REVERSE-PRACTICE-BUUCTF-27

REVERSE-PRACTICE-BUUCTF-27[XMAN2018排位赛]Dragon Quest[羊城杯 2020]easyre[watevrCTF 2019]Repyc[2019红帽杯]calc[XMAN2018排位赛]Dragon Quest elf文件&#xff0c;无壳&#xff0c;ida分析 main函数&#xff0c;读取输入&#xff0c;start_quest函数验证输入&#xff0…

C#的变迁史03 - C# 3.0篇

C# 3.0 (.NET 3.5, VS2008) 第三代C#在语法元素基本完备的基础上提供了全新的开发工具和集合数据查询方式&#xff0c;极大的方便了开发。 1. WPF&#xff0c;WCF&#xff0c;WF 这3个工程类型奠定了新一代.NET开发的客户端模型&#xff0c;通信模型&#xff0c;工作流模型。 …

REVERSE-PRACTICE-BUUCTF-28

REVERSE-PRACTICE-BUUCTF-28[FlareOn6]Memecat Battlestation[b01lers2020]chugga_chugga[INSHack2018]Tricky-Part1[watevrCTF 2019]esreveR[FlareOn6]Memecat Battlestation .Net程序&#xff0c;运行后输入weapon code&#xff0c;用dnSpy打开 在Stage1Form直接找到第一个w…

C#的变迁史04 - C# 4.0 之多线程篇

在.NET 4.0中&#xff0c;并行计算与多线程得到了一定程度的加强&#xff0c;这主要体现在并行对象Parallel&#xff0c;多线程Task&#xff0c;与PLinq。这里对这些相关的特性一起总结一下。 使用Thread方式的线程无疑是比较麻烦的&#xff0c;于是在这个版本中有了改善的版本…

REVERSE-PRACTICE-BUUCTF-29

REVERSE-PRACTICE-BUUCTF-29[FlareOn1]Shellolololol[CFI-CTF 2018]powerPacked[INSHack2018]Tricky-Part2[CFI-CTF 2018]Automated Reversing[FlareOn1]Shellolololol exe程序&#xff0c;直接不能运行&#xff0c;无壳&#xff0c;ida分析 简单F8单步调试发现&#xff0c;在…

C#的变迁史05 - C# 4.0篇

C# 4.0 (.NET 4.0, VS2010) 第四代C#借鉴了动态语言的特性&#xff0c;搞出了动态语言运行时&#xff0c;真的是全面向“高大上”靠齐啊。 1. DLR动态语言运行时 C#作为静态语言&#xff0c;它需要编译以后运行&#xff0c;在编译的过程中&#xff0c;编译器要检查语法的正确性…

REVERSE-PRACTICE-BUUCTF-30

REVERSE-PRACTICE-BUUCTF-30[RCTF2019]DontEatMe[b01lers2020]little_engine[NPUCTF2020]你好sao啊[MRCTF2020]Shit[RCTF2019]DontEatMe exe程序&#xff0c;运行后输入&#xff0c;无壳&#xff0c;用ida分析 交叉引用字符串来到sub_401260函数&#xff0c;读取输入&#xff…

C#的变迁史06 - C# 4.0 之并行处理篇

前面看完了Task对象&#xff0c;这里再看一下另一个息息相关的对象Parallel。 Parallel对象 Parallel对象封装了能够利用多核并行执行的多线程操作&#xff0c;其内部使用Task来分装多线程的任务并试图将它们分配到不同的内核中并行执行。请注意“试图”这个词&#xff0c;Par…

REVERSE-PRACTICE-BUUCTF-31

REVERSE-PRACTICE-BUUCTF-31[羊城杯 2020]login[羊城杯 2020]Bytecode[羊城杯 2020]babyre[ACTF新生赛2020]fungame[羊城杯 2020]login exe程序&#xff0c;运行后输入&#xff0c;无壳&#xff0c;ida分析 没找到主要逻辑&#xff0c;在字符串窗口看到一些“py”的字样&#…

C#的变迁史07 - C# 4.0 之线程安全集合篇

作为多线程和并行计算不得不考虑的问题就是临界资源的访问问题&#xff0c;解决临界资源的访问通常是加锁或者是使用信号量&#xff0c;这个大家应该很熟悉了。 而集合作为一种重要的临界资源&#xff0c;通用性更广&#xff0c;为了让大家更安全的使用它们&#xff0c;微软为我…

PWN-PRACTICE-BUUCTF-1

PWN-PRACTICE-BUUCTF-1test_your_ncripwarmup_csaw_2016ciscn_2019_n_1test_your_nc 附件的main函数直接system("/bin/sh")&#xff0c;nc直接连即可cat flag rip main函数中&#xff0c;gets函数读取一行会造成栈溢出 构造payload覆盖rip&#xff0c;使得return…

C#的变迁史08 - C# 5.0 之并行编程总结篇

C# 5.0 搭载于.NET 4.5和VS2012之上。 同步操作既简单又方便&#xff0c;我们平时都用它。但是对于某些情况&#xff0c;使用同步代码会严重影响程序的可响应性&#xff0c;通常来说就是影响程序性能。这些情况下&#xff0c;我们通常是采用异步编程来完成功能&#xff0c;这在…

REVERSE-PRACTICE-CTFSHOW-1

REVERSE-PRACTICE-CTFSHOW-1逆向签到题re2逆向4逆向5逆向签到题 ida打开即可得到明文flag re2 附件是一个加密过的flag文本和勒索病毒exe 运行程序&#xff0c;输入1&#xff0c;回车&#xff0c;直接退出&#xff0c;ida分析 选项1的逻辑为&#xff0c;打开flag.txt和enfl…

C#的变迁史09 - C# 5.0 之调用信息增强篇

Caller Information CallerInformation是一个简单的新特性&#xff0c;包括三个新引入的Attribute&#xff0c;使用它们可以用来获取方法调用者的信息&#xff0c; 这三个Attribute在System.Runtime.CompilerServices命名空间下&#xff0c;分别叫做CallerMemberNameAttribute&…

REVERSE-PRACTICE-CTFSHOW-2

REVERSE-PRACTICE-CTFSHOW-2re3红包题 武穆遗书数学不及格flag白给re3 main函数&#xff0c;分析可知&#xff0c;将输入的字符串按十六进制转成数字&#xff0c;写到v5&#xff0c;赋给v17[6] 当i等于6时&#xff0c;v16会加上输入的值&#xff0c;然后进入循环&#xff0c;最…

C#的变迁史10 - C# 5.0 之其他增强篇

1. 内置zip压缩与解压   Zip是最为常用的文件压缩格式之一&#xff0c;也被几乎所有操作系统支持。在之前&#xff0c;使用程序去进行zip压缩和解压要靠第三方组件去支持&#xff0c;这一点在.NET4.5中已有所改观&#xff0c;Zip压缩和解压功能已经内置于框架本身。这个功能使…

REVERSE-PRACTICE-CTFSHOW-3

REVERSE-PRACTICE-CTFSHOW-3签退神光签到baby_gay签退 .pyc文件&#xff0c;uncompyle6反编译&#xff0c;得到python源码&#xff0c;分析写在源码注释中 先变表base64&#xff0c;再凯撒加密&#xff0c;向后移动2位 import string c_charset string.ascii_uppercase str…

REVERSE-PRACTICE-CTFSHOW-4

REVERSE-PRACTICE-CTFSHOW-4encodeEasyBJD hamburger competitionJustREencode elf文件&#xff0c;upx脱壳&#xff0c;ida分析 交叉引用字符串"Please input your flag:"&#xff0c;来到sub_804887C函数 输入经过三次变换&#xff0c;先是变表base64&#xff0c;…

CSS 基础框盒模型介绍

当对一个文档进行布局&#xff08;lay out&#xff09;的时候&#xff0c;浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型&#xff08;CSS basic box model&#xff09;&#xff0c;将所有元素表示为一个个矩形的盒子&#xff08;box&#xff09;。CSS 决定这些盒子的大小…

REVERSE-PRACTICE-CTFSHOW-5

REVERSE-PRACTICE-CTFSHOW-5re2_归心Mud[吃鸡杯]ezmore[吃鸡杯]有手就行re2_归心 exe程序&#xff0c;运行后要求输入flag&#xff0c;ida分析 函数窗没找到主逻辑函数&#xff0c;shiftF12看字符串窗口 发现有java/lang/String&#xff0c;com/exe4j/runtime/WinLauncher等字…