深入理解C#(第3版)-- 【C#2】第7章 结束C# 2的讲解:最后的一些特性(学习笔记)
分部类型——可以在多个源文件中为一个类型编写代码。特别适用于部分代码是自动生成,而其他部分的代码为手写的类型。
静态类——对工具类进行整理,以便编译器能明白你是否在不恰当地使用它们,并使你的意图更清晰。
独立的取值方法/赋值方法属性访问器——属性终于有了公有取值方法和私有赋值方法了!(这不是唯一的组合,不过这是最常见的组合。)
命名空间别名——在类型名称不唯一的情况下的一种解决方式。
Pragma指令——用于操作的特定编译器指令,如禁止对某一特殊代码段使用特定的警告信息。
固定大小的缓冲区——在非安全代码中,可以更多地控制结构处理数组的方式。
InternalsVisibleToAttribute(友元程序集)——跨越语言、框架和运行时的一个特性,在需要时能对选定的程序集进行更多的访问。
7.1 分部类型
C# 2允许多个文件为一个类型提供代码,由多个源代码文件组成的类型称为分部类型。
7.1.1 在多个文件中创建一个类型
创建分部类型只需在涉及的每个文件的类型的声明部分附加一个上下文关键字partial。
任何文件都能指定要实现的接口(不过不必在那个文件中实现)基类型以及类型参数约束。然而,如果多个文件都设定了基类型,那么它们必须是相同的,并且如果多个文件都设定了类型参数约束,那么约束必须是一致的。
分部类型必须具有相同的访问修饰符.
7.1.2 分部类型的使用
分部类型主要连接设计器和其他代码生成器,利用分部类型模型,代码生成器可以拥有自由操作的文件,或者只要它愿意可以每次都重写整个文件。
分部类型还有一种稍微不同的用法,就是辅助进行重构。某些时候,一个类型变得太大,担当了太多职责,就应对其进行重构。
7.1.3 C# 3独有的分部方法
如生成代码可以提供一个基类,该基类具有一些默认情况下不完成任何事情的虚方法。手动生成的代码就可以从这个类派生,并覆盖部分或全部方法。
如果没有实现它们,也不会产生任何开销——任何对未实现的分部方法的调用,都会被编译器移除。
代码清单7-2 分部方法在构造函数中被调用
// Generated.cs using System; partial class PartialMethodDemo { public PartialMethodDemo() { OnConstructorStart(); Console.WriteLine("Generated constructor"); OnConstructorEnd(); } partial void OnConstructorStart(); partial void OnConstructorEnd(); } // Handwritten.cs using System; partial class PartialMethodDemo { partial void OnConstructorEnd() { Console.WriteLine("Manual code"); } }
分部方法的声明方式与抽象方法相同:只使用partial修饰符提供签名而无须任何实现。同样,实际的实现还需要partial修饰符,不然就和普通方法一样了。
由于方法可能不存在,分部方法返回类型必须为void,且不能获取out 参数。它们必须是私有的,但可以是静态的或泛型的。
7.2 静态类型
工具类的主要特性如下:
所有的成员都是静态的(除了私有构造函数);
类直接从object派生;
一般情况下不应该有状态,除非涉及高速缓存或单例;
不能存在任何可见的构造函数;
类可以是密封的(添加sealed修饰符),当然要开发人员记得这样做。
代码清单7-3 典型的C# 1 工具类
public sealed class NonStaticStringHelper //密封类以防止派生 { private NonStaticStringHelper() //防止其他代码对其进行实例化 { } public static string Reverse(string input) //所有的方法都是静态的 { char[] chars = input.ToCharArray(); Array.Reverse(chars); return new string(chars); } }
如果你不为类提供任何构造函数,那么C# 1 编译器总是会提供一个公有的默认的无参构造函数。
代码清单7-4 将代码清单7-3中的工具类转化为一个C# 2 静态类
using System; public static class StringHelper { public static string Reverse(string input) { char[] chars = input.ToCharArray(); Array.Reverse(chars); return new string(chars); } }
在类声明中使用了static修饰符而不是sealed,C# 2 编译器知道静态类不用包含任何构造函数,所以它也不会提供默认的。
实际上,编译器在类定义上执行了大量的约束:
类不能声明为abstract 或sealed ,虽然两者都是隐含声明的;
类不能设定任何要实现的接口;
类不能设定基类型;
类不能包含任何非静态成员,包括构造函数;
类不能包含任何操作符;
类不能包含任何protected或protected internal 成员。
应当注意,即使所有成员都必须为静态的,你还是要把它们都显式地声明为静态的,除了嵌套类型和常量。虽然嵌套类型是外围类的隐式静态成员,不过如果不要求的话,嵌套类型本身可以不用是静态的。
7.3 独立的取值方法/ 赋值方法属性访问器
C# 1 不允许为属性设定一个公开的取值方法和一个私有的赋值方法,在C# 1 中取值方法和赋值方法必须具有相同的可访问性——它作为属性声明的一部分进行声明,而不是作为取值方法或赋值方法声明的一部分进行声明的。
通常,在改变属性支持的变量时,你可能需要进行一些验证、日志记录、锁定或者执行其他代码,但是你不希望让属性对于类的外部代码是可写的。
string name; public string Name { get { return name; } private set { // Validation, logging etc here name = value; } }
注意,你不能把属性本身声明为私有,而让取值方法是公有的——你只能设置比属性更私有的特殊取值方法/ 赋值方法。
7.4 命名空间别名
C# 1 的using 指令(不要和自动调用Dispose的using 语句混淆)能够用于两种情况——一种是为命名空间和类型创建一个别名(例如,using Out=System.Console;),另外一种就是将一个命名空间引入到当编译器查找某个类型(例如,using System.IO;)时可以搜索到的上下文列表中。
7.4.1 限定的命名空间别名
C#2 引入了“::”命名空间别名修饰符.(缓解别名和类名相同情况的冲突使用)
代码清单7-6 使用::来告知编译器使用别名
using System; using WinForms = System.Windows.Forms; using WebForms = System.Web.UI.WebControls; class WinForms {} class Test { static void Main() { Console.WriteLine(typeof(WinForms::Button) ); Console.WriteLine(typeof(WebForms::Button) ); } }
7.4.2 全局命名空间别名(global::)
代码清单7-7 使用全局命名空间别名来准确地设定想要的类型
using System; class Configuration {} namespace Chapter7 { classConfiguration {} classTest { static void Main() { Console.WriteLine(typeof(Configuration)); Console.WriteLine(typeof(global::Configuration)); Console.WriteLine(typeof(global::Chapter7.Test)); } } }
7.4.3 外部别名
代码清单7-8 调用在不同程序集中,具有相同名称的不同类型
// Compile with // csc Test.cs /r:FirstAlias=First.dll /r:SecondAlias=Second.dll extern alias FirstAlias;//指定外部别名 extern alias SecondAlias;//指定外部别名 using System; using FD = FirstAlias::Demo;//使用命名空间别名来使用外部别名 class Test { static void Main() { Console.WriteLine(typeof(FD.Example));//使用命名空间别名 Console.WriteLine(typeof(SecondAlias::Demo.Example));//直接使用外部别名 } }
7.5 pragma 指令
pragma 指令就是一个由#pragma开头的代码行所表示的预处理指令,它后面能包含任何文本。
微软C#编译器理解两种pragma 指令:警告(warning)和校验和(checksum )。
7.5.1 警告pragma
代码清单7-9 包含了未用字段的类
public class FieldUsedOnlyByReflection { int x; }
如果你尝试编译代码清单7-9,将获得一个类似这样的警告消息:
FieldUsedOnlyByReflection.cs(3,9): warning CS0169:
The private field 'FieldUsedOnlyByReflection.x' is never used
代码清单7-10 禁用(和恢复)警告 CS0169
public class FieldUsedOnlyByReflection { #pragma warning disable 0169 int x; #pragma warning restore 0169 }
7.5.2 校验和pragma
校验和pragma 的语法如下:
#pragma checksum "filename" "{guid}" "checksum bytes"
7.6 非安全代码中固定大小的缓冲区
fixed
7.7 把内部成员暴露给选定的程序集
7.7.1 简单情况下的友元程序集
InternalsVisibleToAttribute
代码清单7-12 友元程序集的演示
// Compiled to Source.dll using System.Runtime.CompilerServices; [assembly:InternalsVisibleTo("FriendAssembly")] //授予额外的访问权限 public class Source { internal static void InternalMethod() {} public static void PublicMethod() {} } // Compiled to FriendAssembly.dll public class Friend { static void Main() { Source.InternalMethod(); //在FriendAssembly中使用额外的访问权限 Source.PublicMethod(); } } // Compiled to EnemyAssembly.dll public class Enemy { static void Main() { // Source.InternalMethod(); //EnemyAssembly 不具备特殊的访问权限 Source.PublicMethod(); //依旧可以正常访问公有方法 } }
7.7.2 为什么使用InternalsVisibleTo
7.7.3 InternalsVisibleTo 和签名程序集
浙公网安备 33010602011771号