c#新特性的应用需要我们深入的学习以及在团队范围内的知识共享,这样才不致于让代码显得怪异。
本文内容包括:
1.c#1.1过渡到2.0时的新特性回顾
2.c#3.0新特性简单介绍
c#2.0新特性
范型
我们知道通用的数据结构可以采用 object 存储任何数据类型。使用 object 问题是:
显示的强制转带来的代码复杂性
换装箱拆箱的性能损失(为什么有性能损失?因为涉及动态内存分配和运行时类型检查)。还有一些运行时才会出现的类型转换异常也是我们难以在代码编写的时候能够检查到的,防不胜防。
范型应时而生,它的思路是什么呢?它接受带有类型参数并存储这个类型而不转换它,类型参数在类名字后的 <T> 中指定。 T 相当于一个占位符,直到使用的时候才指定一个实际的类型。确切的说当应用程序首次创建一个构造范型类型的实例时, .net 公共语言运行时的实时编译器 JIT 将在进程中把范型 IL 和元数据转化为本地代码并把类型参数转化为实际的类型。对于这个泛型类型的后续引用将会使用相同的本机代码。这也就是传说中的范型类型实例化。
看一段代码: 范型的一个有趣话题:约束
问题溯源:由于 T 代表的可能是任何类型,所以我们使用 T 的方法仅限于 Equals GetHasCode ToString ,那么我们要使用某些特定数据类型的方法呢?比如实现了 IComparable 接口的数据类型的 CompareTo 方法?
一种方法是强制转换到 IComparable 接口。这种方法的缺点是: 1. 进行运行时动态类型检查增加了性能上的开销, 2. 自然地如果 key 没有实现 IComparable 接口的异常报告推迟到了运行时
另一种方法就是约束列表,关键词是where ,后面跟的是类或者接口的列表,还有一个可选、特殊的 new ()约束;其实就是说这个类型必须要有一个公开无参构造函数,这就允许泛型类使用这种构造函数来创建实例。约束是一把双刃剑,一方面它提供了编译时类型检查,增强了性能,但是它也限制了范型类型的能力。
范型方法 范型方法在方法名字后面使用 <> 指定一个或者多个类型参数,类型参数可以出现在参数列表,返回类型和方法体内。编译器会使用一种类型推断的机制通过其他参数类推断正确的类型参数。
给出一些范型的例子:
Code 1 public class Test < T > 2 { 3 public T item { get ; set ; } 4 5 public void Display() 6 { 7 Console.WriteLine(item.ToString()); 8 } 9 10 } 11 12 public class Map < K, V > 13 { 14 Dictionary< K, V > d = new Dictionary < K, V > (); 15 public void Add(K key, V value) 16 { 17 18 d.Add(key, value); 19 } 20 public void Display() 21 { 22 foreach (var item in d.Keys) 23 { 24 Console.WriteLine(" Key: " + item + " Value: " + d[item]); 25 } 26 } 27 } 28 29 public class ComparableTest < T > where T : IComparable 30 { 31 public T item { get ; set ; } 32 33 public void Display() 34 { 35 if (item.CompareTo( 10 ) > 0 ) 36 { 37 Console.WriteLine(item.ToString() + " > " + " 10 " ); 38 } 39 else 40 { 41 Console.WriteLine(item.ToString() + " < " + " 10 " ); 42 } 43 } 44 45 } 46 47 public class MapWithConstraint < K, V > where V : new () 48 { 49 Dictionary< K, V > d = new Dictionary < K, V > (); 50 public void Add(K key, V value) 51 { 52 53 d.Add(key, value); 54 } 55 public void Display() 56 { 57 foreach (var item in d.Keys) 58 { 59 Console.WriteLine(" Key: " + item + " Value: " + d[item]); 60 } 61 } 62 } 63 64 public class GenericMethodTest 65 { 66 public T Display < T, V, K > (T t, K k, V v) 67 { 68 Console.WriteLine(t.ToString() + " " + k.ToString() + " " + v.ToString()); 69 return t ; 70 } 71 } 72 public class Student 73 { 74 public int ID { get ; set ; } 75 public string Name { get ; set ; } 76 } 77 class Program 78 { 79 static void Main( string [] args) 80 { 81 Test< int > t = new Test < int > (); 82 t.item = 10 ; 83 t.Display(); 84 85 86 Map< string , int > map = new Map < string , int > (); 87 map.Add(" King " , 23 ); 88 map.Add(" XiaoQiang " , 24 ); 89 map.Display(); 90 91 ComparableTest< int > test = new ComparableTest < int > (); 92 test.item = 123 ; 93 test.Display(); 94 95 96 // ComparableTest<Student > test2 = new ComparableTest<Student >(); 97 98 99 GenericMethodTest g = new GenericMethodTest(); 100 Console.WriteLine(g.Display(23 , 32 , 52 )); 101 102 103 104 Console.ReadLine();105 106 }107 }108
匿名方法
匿名方法其实就是体现了这样一个原则:如无必要,勿增实体;我们在一个简单的 WinForm 环境中来说明这个问题:一个按钮单击事件我们可以这样来定义响应代码。
1 this .button1.Click += delegate 2 { 3 this .label1.Text = " King2002 " ; 4 }; 5 6 this .button1.Click += delegate ( object sender, System.EventArgs arg) 7 { 8 this .label1.Text = " Test " ; 9 }; 10 this .button1.Click += new System.EventHandler( this .button1_Click); 11 private void button1_Click( object sender, EventArgs e) 12 { 13 MessageBox.Show(" Hello " ); 14 }15 16 17
匿名方法使得代码对于委托的实现更加简单,匿名方法还有一个用途就是操作一些私有成员,因为它相当于共享了一部分代码。 这里会有一个疑问:匿名方法和委托类型的隐式转换有什么要求?答案:只要参数列表和委托类型的返回值是兼容的就可以完成转换。
参数列表兼容:无参数或者参数的数量、类型、修饰符严格匹配
无返回类型,所有 return 语句形管的表达式可以被隐式转换到委托的类型
后面我们会看到更简单使用 delegate 的例子。
迭代器
一个对象如果是可枚举的,那么我们可以使用 foreach 语句来遍历其中的元素。实际上是调用了这个对象的 GetEnumberator 方法,它返回一个 enumberator (枚举器)。实现枚举器很难但是我们可以使用迭代器实现!
1 public class DaysOfTheWeek : System.Collections.IEnumerable 2 { 3 string [] m_Days = { " Sun " , " Mon " , " Tue " , " Wed " , " Thr " , " Fri " , " Sat " } ; 4 5 public System.Collections.IEnumerator GetEnumerator() 6 { 7 for ( int i = 0 ; i < m_Days.Length; i ++ ) 8 { 9 yield return m_Days[i]; 10 }11 }12 }13 14 class TestDaysOfTheWeek 15 { 16 static void Main() 17 { 18 // Create an instance of the collection class 19 DaysOfTheWeek week = new DaysOfTheWeek(); 20 21 // Iterate with foreach 22 foreach ( string day in week) 23 { 24 System.Console.Write(day + " " ); 25 }26 }27 }28
上面是 MSDN 上的一个简单的例子,有了直观的印象我们可以深究一点:
迭代器是产生值的有序序列的一个语句块 , 迭代器不是一种成员,它只是实现函数成员的方式。 Yield return 语句产生迭代的下一个值 yield break 语句指明迭代已经完成;GetEnumerator返回值只要是枚举器接口或者是可枚举接口(System.Collections. IEnumerable System.Collections. IEnumerator System.Collections.Generic.IEnumerable<T> System.Collections.Generic.IEnumerator<T> ),迭代器就可以被用做函数体。
不完整类型 不完整类型完全是为了更好的进行代码管理。仔细观察我们现在添加一个页面时,它的后代代码就使用了不完整类型:
public partial class _Default : System.Web.UI.Page
c#3.0新特性
自动实现属性 Auto-Implemented Properties
1 public class Card 2 3 { 4 5 // 这样的属性是不是简单多了? 6 7 public int ID { get ; set ; } 8 9 public string Password { get ; set ; } 10 11 }12 13 class Customer 14 15 { 16 17 public double TotalPurchases { get ; set ; } 18 19 public string Name { get ; private set ; } // read-only 20 21 public int CustomerID { get ; private set ; } // read-only 22 23 }24
自动属性必须包含 get set ,如果是只读的就添加 private 关键字
Attributes 不允许使用自动属性,这里还是推荐使用常规的属性书写方式
< ms-help://MS.MSDNQTR.v90.en/dv_csref/html/aa55fa97-ccec-431f-b5e9-5ac789fd32b7.htm >
检索表达式
Query Expressions
LINQ 毫无疑问是 c#3.0 最抢眼的东西,关于 LINQ 的文章已经很多,这里不展开,详细资料请参考:
< ms-help://MS.MSDNQTR.v90.en/dv_csref/html/40638f19-fb46-4d26-a2d9-a383b48f5ed4.htm >
int [] items = new int [] { 1 , 2 , 3 , 4 , 5 } ; IEnumerable < int > ints = from item in items where item > 2.5 select item; foreach (var p in ints) { Console.WriteLine(p); }
隐式类型变量 Implicitly Typed Variables (var)
内部变量的类型可以使用 var 而不是确切的类型。 var 关键字可以指示编译器通过右侧的初始化部分来推断实际的数据类型。
var num = 5 ; var a = new [] { 2 , 5 , 6 , 7 } ; // anon 被编译成匿名类型 注意下面的Name Age都没有定义 var anon = new { Name = " Terry " , Age = 34 } ; var list = new List < int > (); using (var file = new System.IO.StreamReader( @" D:\http.txt " , Encoding.Default)) { Console.WriteLine(file.ReadLine()); Console.WriteLine(anon.Name ); Console.WriteLine(anon.Age ); }
var 的使用会有一些约束: < ms-help://MS.MSDNQTR.v90.en/dv_csref/html/b9218fb2-ef5d-4814-8a8e-2bc29b0bbc9b.htm >
对象、集合初始化
Object and Collection Initializers
设计完成之后生成了一些类代码,但是这里有一个问题就是属性代码的填写,在 FX3.0 属性代码的已经简化 ,
类的初始化也提供了更简单的书写方式;可以看一下下面代码段中 c2 的初始化,列表对象的初始化按照同样的格式也会大大的简化。
1 public class Card 2 3 { 4 5 // 这样的属性是不是简单多了? 6 7 public int ID { get ; set ; } 8 9 public string Password { get ; set ; } 10 11 }12 13 class Program 14 15 { 16 17 static void Main( string [] args) 18 19 { 20 21 // 通常我们初始化一个对象是这样做的 22 23 Card c = new Card(); 24 25 c.ID = 2002 ; 26 27 c.Password = " henghenghaxi " ; 28 29 30 31 // 现在我们可以这样做 32 33 Card c2 = new Card { ID = 23 , Password = " testnum " } ; 34 35 36 37 Console.WriteLine(c.ID.ToString() + " - " + c.Password); 38 39 40 41 // 列表的初始化 42 43 List< Card > cards = new List < Card > 44 45 { 46 47 new Card {ID = 12 ,Password = " 12sfdgr " } , 48 49 new Card {ID = 13 ,Password = " 12sdfwsd " } , 50 51 new Card {ID = 14 ,Password = " 12jkhjh " } , 52 53 new Card {ID = 15 ,Password = " 1dfsed2 " } , 54 55 new Card {ID = 16 ,Password = " 1sdfsd2 " } , 56 57 58 59 }; 60 61 // 看下面的例子 62 63 List< int > digits = new List < int > { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ; 64 65 List< int > digits2 = new List < int > { 0 + 1 , 12 % 3 , MakeInt() } ; 66 67 68 69 Console.WriteLine(cards.Count.ToString());70 71 Console.ReadLine();72 73 }74 75 static int MakeInt() 76 77 { 78 79 return DateTime.Now.Second; 80 81 }82 83 84 85 }86 87
匿名类型
Anonymous Types
匿名类型把一系列的只读属性封装在一个对象里面,而并没有指定这个对象的类型编译器会给这个对象类型一个名字,但是这个名字在代码级别是不可用的。
看下面的代码:
// anon 被编译成匿名类型 注意下面的Name Age都没有定义
var anon = new { Name = "Terry", Age = 34 };
//anon.Age = 23;
最后一行如果执行编译器会给出一个错误:
Property or indexer 'AnonymousType#1.Age' cannot be assigned to -- it is read only
这个错误信息可以印证上面的说法。继续扩展上面的代码,一起看:
var card = from c3 in cards where c3.ID > 13 // select c3; select new {MyID = c3.ID,MyPassword = c3.Password } ; foreach (var item in card ) { Console.WriteLine(item.MyID.ToString() + " --- " + item.MyPassword ); }
这里的代码是匿名类型的一个典型应用,详细请查阅:
< ms-help://MS.MSDNQTR.v90.en/dv_csref/html/59c9d7a4-3b0e-475e-b620-0ab86c088e9b.htm >
扩展方法
Extension Methods
扩展方法是一个静态方法,可以关联在一种类型上,所以这个方法可以在他处调用。这样仿佛给某一个类型添加了方法!而实际上我们并没有改变原有的代码。
详情参阅: ms-help://MS.MSDNQTR.v90.en/dv_csref/html/175ce3ff-9bbf-4e64-8421-faeb81a0bb51.htm
public static class Test
{
public static string RemoveWhiteSpace(this string s)
{
return s.Replace(" ", "");
}
}
定义了上面的方法之后,我们可以在 string 类型上使用这个方法下面是在开发环境中的截图:
匿名方法
Anonymous Functions
1 delegate void Testdelegate( string s); 2 3 static void Show( string s) 4 5 { 6 7 Console.WriteLine(s); 8 9 }10 11 // 1.1里面我们这样做 12 13 Testdelegate t = new Testdelegate(Show); 14 15 16 17 // 2.0 18 19 Testdelegate t2 = delegate ( string str) { Console.WriteLine(str); } ; 20 21 22 23 // 3.0 24 25 Testdelegate t3 = (X) => { Console.WriteLine(X); } ; 26 27 t(" Kingtest1 " ); 28 29 t2(" 20022396 " ); 30 31 t3(" 20022458 " ); 32
在 3.0 里面我们的代码使用了 Lambda 表达式,详情参阅:
ms-help://MS.MSDNQTR.v90.en/dv_csref/html/57e3ba27-9a82-4067-aca7-5ca446b7bf93.htm
Lambada 表达式在 C#3.0 中的典型应用:
ms-help://MS.MSDNQTR.v90.en/fxref_system.core/html/5a7a3466-7d99-dea6-a9fa-04043f951573.htm
// Split the string into individual words. string [] words = sentence.Split( ' ' ); // Prepend each word to the beginning of the // new sentence to reverse the word order. string reversed = words.Aggregate((workingSentence, next) => next + " " + workingSentence); bool b = words.All(w => w.Length > 5 ); bool b2 = words.Any(w => w.Length > 10 );
一句话体会:
c#新特性的应用需要我们深入的学习以及在团队范围内的知识共享,这样才不致于让代码显得怪异。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述