[リーディングノート] C#スタディノート5:C#3.0自動属性、匿名属性、拡張方法
序文
この章は、この本を読むことで得られる最大のメリットと見なすことができます。ラムダ式は、試行錯誤の末に使用されます。C#3.0は、コーディングスタイルを覆すと言えます。ラムダは多くのスペースを必要とするため、C#3.0スマートコンパイルを要約します。次回Lambda式を個別に紹介するときに、ブラウザーによってもたらされる多くの利点。この記事には、主に、 自動プロパティ、暗黙型、オブジェクトコレクションの初期化、匿名型、拡張メソッドが含まれます。
C#3.0によってもたらされた変更を見てみましょう。
1.自動的に実装される属性は
C#3.0より前です。属性を定義するときは、通常、次のようなコードを記述します。
1 class Person 2 {3 //プライベートフィールドを定義する4プライベート文字列名; 5 6 //読み取り可能および書き込み可能な属性を定義する7public string Name 8 {9 get {return name;} 10 set {name = value;} 11} 12 13 private int age; 14 //読み取り専用属性を定義する15public int Age16 {17 get {return age;} 18} 19}
PS:ここにショートカットキーがあります:プライベート文字列名;名前を選択してからCtrl + R + Eを選択すると、Name属性がすばやく生成されます。
C#3.0以降、追加の検証を必要としないプロパティ(追加の検証を必要とするプロパティは以前の方法で定義する必要があります)の場合、自動実装の機能を使用してプロパティの定義を簡素化できます。現時点では追加の定義は必要ありません。プライベートフィールド。コードは次のとおりです。
1 class Person2 {3 //自動的に実装された属性を使用して属性を定義する4 //読み取り/書き込み属性を定義する5public string Name {get; set;} 6 //読み取り専用属性を定義する7public int Age {get; private set; } 8}
PS:ここにもショートカットキーがあります:propと入力し、Tabキーを2回クリックして上記のプロパティを生成しますが、値を手動で変更する必要があります。
同様のショートカットキーは次のとおりです。cwと入力し、Tabキーを2回クリックして、コンソールを直接生成します。 .WriteLine();.似たようなものがたくさんあるので、ここではリストしません。
この方法で属性を定義できる理由は、主にコンパイラがコンパイル時にプライベートフィールドを作成するためです。デコンパイラツールを使用すると、自動的に実装された属性を使用するときに、C#が必要なフィールドの作成に役立つことがわかります。
さらに、構造内で自動属性を使用する場合、すべてのコンストラクターがパラメーターなしのコンストラクターthisを明示的に呼び出す必要があることに注意してください。そうしないと、コンパイルエラーが発生します。この方法でのみ、コンパイラーはすべてのフィールドが割り当てられていることを知ることができます。
ただし、クラスパラメータなしのコンストラクタを明示的に呼び出す必要はありません。これは主にC#コンパイラの設計によって決定され、覚えているだけです。
1 public struct TestPerson 2 {3 public string Name {get; set;} 4 public int Age {get; private set;} 5 6 //構造内で、パラメーターなしのコンストラクターthis()が明示的に呼び出されていない場合、yesコンパイル時エラーが発生しました7public TestPerson(string name)8:this()9 {10 this.Name = name; 11} 12}
2,隐式类型 var关键字
C#是强类型语言, 在定义一个变量时, 需要声明变量的类型. 然而类型的长度过长,就可能影响代码的可读写性. 为了避免这样的问题, C#3.0 引入了隐式类型,即可以使用关键字var来声明变量或数组.
var关键字告诉编译器去根据变量的值来推断其类型.
1 class Program2 {3 static void Main(stirng[] args)4 {5 //用var声明局部变量6 var stringValue = "Barry Wang";7 stringValue = 2;8 }9 }
这里就会报错"无法将类型int 隐式转换为string". 调试会发现stringValue是string类型的.
使用隐式类型有一些限制, 包括以下几点:
(1)被声明的变量是一个局部变量, 不能为字段
(2)变量在声明时必须被初始化, 因为编译器要根据变量的赋值来推断类型
(3)变量不能初始化为一个方法组, 也不能为一个匿名函数
3,对象集合初始化
在C#3.0之前定义类, 我们往往需要定义多个构造函数来完成不同情况下的初始化, C#3.0 提供了对象初始化器, 它减少了我们在类中定义的构造函数代码, 从而使代码更加简洁.
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //没有对象初始化器时的代码 6 Person p = new Person("BarryWang", 25); 7 p.Weight = 60; 8 p.Height = 170; 9 }10 }11 12 public class Person13 {14 public string Name { get; set; }15 public int Age { get; set; }16 public int Weight { get; set; }17 public int Height { get; set; }18 19 //定义不同情况下的初始化函数20 public Person() { }21 public Person(string name) { } 22 public Person(string name, int age) { }23 public Person(string name, int age, int weight) { }24 public Person(string name, int age, int weight, int height) 25 {26 this.Name = name;27 this.Age = age;28 this.Weight = weight;29 this.Height = height;30 }31 }
在C#3.0引入对象初始化之后, 我们就不需要定义多个构造函数了, 前面的代码可以简化为如下的形式:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //使用对象初始化器初始化 6 Person p = new Person() {Name = "Barry Wang", Age = 25, Weght = 60, Height = 70}; 7 //下面这行代码和上面一行是等价的, 只不过下面省略了构造函数的圆括号而已 8 Person p2 = new Person {Name = "Barry Wang", Age = 25, Weght = 60, Height = 70}; 9 }10 }11 12 public class Person13 {14 public string Name { get; set; }15 public int Age { get; set; }16 public int Weight { get; set; }17 public int Height { get; set; }18 }
利用反编译工具可以知道编译器为对象做了如下处理: 首先C#编译器生成了一个Person类的临时对象, 并调用Person类的默认无参构造函数对其初始化.然后对它的属性逐个赋值.
由此可以想到,要使用对象初始化器,则必须保证类中具有一个无参构造函数. 如果我们自定义了一个有参构造函数而把默认的无参构造函数覆盖了, 则需要重新定义一个无参构造函数.
再例如 给List 中添加元素, 在C#3.0 之前我们需要一个个Add 添加, 而现在直接可以利用集合初始化器即可, 编译器会调用Add方法, 一个个地将初始化的内容添加进去.
1 class Program2 {3 List<string> newNames = new List<string>4 {5 "A", "B", "C"6 };7 }
4,匿名类型
匿名类型顾名思义就是没有知名类型的类型, 通过隐式类型和对象初始化器两种特性创建了一个类型未知的对象, 使我们在不定义类型的情况下可以实现对象的创建,从而减少了类定义过程的代码.
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //定义匿名类型对象 6 var person = new {Name = "Barry Wang", Age = 25 }; 7 Console.WriteLine("{0} 的年龄为: {1}", person.Name, person.Age); 8 9 //定义匿名类型数组10 var personCollection = new[]11 {12 new {Name = "Barry", Age = 30},13 new {Name = "Brad", Age = 22},14 new {Name = "Tony", Age = 25}15 };16 17 int totalAge = 0;18 foreach (var p in personCollection)19 {20 //下面一行代码证明了Age属性时int类型21 totalAge += p.Age;22 }23 24 Console.WriteLine("所有人的年龄总和为: {0}", totalAge);25 Console.ReadKey();26 }27 }
5,扩展方法
扩展方法, 首先是一个方法, 他可以用来扩展已定义类型中的方法成员.
如下例所示:
1 public static class Extensions 2 { 3 //对string 类扩展,注意this关键字 4 public static void TestStr(this string s) 5 { 6 Console.WriteLine(s.ToString()); 7 } 8 //对int 类扩展 9 public static void TestInt(this int i, string s)10 {11 Console.WriteLine(s + i.ToString());12 }13 }14 class Program15 {16 static void Main(string[] args)17 {18 //使用扩展方法19 string s = "Test Extensions Methods";20 s.TestStr();//调用方法121 Extensions.TestStr(s);//调用方法222 int i = 100;23 i.TestInt("This value is ");24 Extensions.TestInt(i, "This value is ");25 Console.ReadKey();26 }27 }
并不是所有的方法都可以用作扩展方法, 我们需要考察它是否符合下列扩展方法的定义规则:
(1)扩展方法必须在一个非嵌套, 非泛型的静态类中定义
(2)它至少要有一个参数
(3)第一个参数必须加上this关键字作为前缀(第一个参数类型也称为扩展类型, 即指方法对这个类型进行扩展)
(4)第一个参数不能使用任何其他修饰符(如不能使用ref out等)
1 namespace CurrentNameSpace 2 { 3 //要想使用不用命名空间下的扩展方法, 需要先引入该命名空间 4 using CustomNameSpace; 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Person p = new Person {Name = "BarryWang"};10 p.Print();//等价于==>ExtensionClass.Print(p);11 p.Print("Hello");12 Console.ReadKey();13 }14 }15 16 //自定义类型17 public class Person18 {19 public string Name { get; set; }20 }21 22 //当前命名空间下的扩展方法定义23 public static class ExtensionClass24 {25 public static void Print(this Person p)26 {27 Console.WriteLine("调用的是当前命名空间下的扩展方法输出, 姓名为: {0}", p.Name);28 }29 }30 }31 32 namespace CustomNameSpace33 {34 using CurrentNameSpace;35 public static class CustomExtensionClass36 {37 //扩展方法定义38 public static void Pint(this Person p)39 {40 Console.WriteLine("调用的是CustomNameSpace命名空间下扩展方法输出: 姓名为: {0}", p.Name);41 }42 43 //扩展方法定义44 public static void Pint(this Person p, string s)45 {46 Console.WriteLine("调用的是CustomNameSpace命名空间下扩展方法输出: 姓名为: {0},附加字符串为{1}", p.Name, s);47 }48 }49 }
打印结果是:
调用的是当前命名空间下的扩展方法输出, 姓名为: Barry Wang
调用的是CustomNameSpace命名空间下扩展方法输出, 姓名为: Barry Wang, 附加字符串为Hello
注解: 大家看到p.Print(); 是否有些疑惑呢? 对于C#3.0编译器而言, 当它看到某个类型的变量在调用方法时, 它会首先去该对象的实例方法进行chazhao,如果没有找到与调用方法同名并参数一致的实例方法,
编译器就回去查找是否存在合适的扩展方法. 编译器会检查所有导入的命名空间和当前命名空间中的扩展方法, 并将变量类型匹配到扩展类型.
从编译器发现扩展方法的过程来看, 方法调用的优先级别顺序为: 类型的实例方法-->当前命名空间下的扩展方法-->导入命名空间的扩展方法.
解释上面代码打印结果的由来: 在以上代码中存在另个不同的命名空间, 它们都定义了带一个参数的扩展方法Print. 根据执行顺序, 编译器会首先查看Person类型中是否定义了无参的Print实例方法.
如果有, 则停止查找; 否则继续查找当前命名空间下, 即CurrentNameSpace下是否定义了一个带参数的扩展方法Print.
此时在CurrentNameSpace命名空间下, 正好存在带一个扩展类型参数的Print扩展方法, 编译器因此不会再继续查找其他引入命名空间了. 所以p.Print() 调用的是当前命名空间下的Print扩展方法.
而p.Print("Hello")的调用也是一个道理.
PS: 终于把今天的两篇博文更新完了, 这么冷的天 真是不容易, 记录到这里希望自己以后有时间还会再看看吧!
カテゴリ: C#ベーシックシリーズ、 読書ノート