8.1 实例构造器和类(引用类型)

  • 构造引用类型的对象时,在调用类型的实例构造器之前,为对象分配的内存总是先被归零 。没有被构造器显式重写的所有字段都保证获得 0 或 null 值。

  • 构造器不能被继承。不能使用以下修饰符: virtual,new,override,sealed和abstract.

  • 如果基类没有提供无参构造器,那么派生类必须显式调用一个基类构造器,否则编译器会报错。

  • 如果类的修饰符为 static(sealed 和 abstract),编译器不会生成默认构造器。(静态类在元数据中是抽象密封类)

  • 为了使代码“可验证”,类的实例构造器在访问从基类继承的任何字段之前,必须先调用基类的构造器

  • 如果派生类的构造器没有显式调用一个基类构造器,C#编译器会自动生成对默认的基类构造器的调用。

  • 极少数时候可以在不调用实例构造器的前提下创建类型的实例(如 MemberwiseClone 方法)。

  • 不要在构造器中调用虚方法。 原因是假如被实例化的类型重写了虚方法,就会执行派生类型对虚方法的实现。但在这个时候,尚未完成对继承层次结构中的所有字段的初始化(被实例化的类型的构造器还没有运行呢)。所以,调用虚方法会导致无法预测的行为。

  • 构造器中,先执行以“内联”方式对字段的初始化(C#编译器将这种语法转换成构造器方法中的代码来执行初始化),再调用基类的构造器,最后执行当前构造器中的代码。详见下面代码:

    public class AType {
public AType()
{
Console.WriteLine(nameof(AType));
}
} public class Ball
{
public Ball()
{
Console.WriteLine(nameof(Ball));
}
} public class Basketball:Ball
{
//内联语法,最终会把atype的初始化放在构造函数中
AType atype = new AType();
public Basketball()
{
Console.WriteLine(nameof(Basketball)); }
}
public class Program
{
static void Main()
{
Basketball ball = new Basketball();
Console.ReadLine();
}
}

输出结果为: AType,Ball,Basketball

  • 然后又做了下面的BT测试:
    public class AType
{
public AType(string x)
{
Console.WriteLine(nameof(AType) + ":" + x);
}
} public class Ball
{
//在基类中新增加个内联方式初始化的字段
private AType atype = new AType(nameof(Ball));
public Ball()
{
Console.WriteLine(nameof(Ball));
}
} public class Basketball : Ball
{
//内联语法,最终会把atype的初始化放在构造函数中
private AType atype = new AType(nameof(Basketball));
public Basketball()
{
Console.WriteLine(nameof(Basketball)); }
}
public class Program
{
static void Main()
{
Basketball ball = new Basketball();
Console.ReadLine();
}
}

输出结果为:AType:Basketball, AType:Ball, Ball, Basketball

  • 使用“内联”方式初始化字段,要注意代码的膨胀效应。如果类中有多个构造器,编译器会把“内联”初始化代码插到每一个构造器中,再插入基类构造器的调用,最后执行自己的代码。

  • 基于上面一点,可考虑不在定义字段时初始化,而是创建单个构造器来执行这些公共的初始化。然后,让其他构造器利用 this 显式调用这个公共初始化构造器,如下面代码:

    internal sealed class SomeType
{
//不要显式初始化下面的字段
private int m_x;
private string m_s;
private double m_d;
private byte m_b; //该构造器将所有字段都设为默认值,
//其他所有构造器都显式调用该构造器
public SomeType()
{
m_x = 5;
m_s = "Hi there";
m_d = 3.14159;
m_b = 0xff;
} //该构造器将所有的字段都设为默认值,然后修改m_x
public SomeType(int x) : this()
{
m_x = x;
} //该构造器将所有的字段都设为默认值,然后修改m_s
public SomeType(string s) : this()
{
m_s = s;
} //该构造器将所有的字段都设为默认值,然后修改m_x和m_s
public SomeType(int x, string s) : this()
{
m_x = x;
m_s = s;
}
}

8.2 实例构造器和结构(值类型)

  • C#编译器根本不会为值类型内联(嵌入)默认的无参构造器。结构也不能包含显式的无参数构造函数(编译器会报错)。

  • 考虑到性能,CLR不会为包含在引用类型中的每个值类型字段都主动调用构造器。值类型的实例构造器只有显式调用才会执行。

  • 值类型的任何构造器都必须初始化值类型的全部字段。(如未全部初始化,编译器会报错)

8.3 类型构造器

  • 类型构造器(静态构造器) 可应用于接口(C#编译器不允许)、引用类型和值类型(但请永远不要在值类型中定义类型构造器,因为CLR有时不会调用值类型的静态类型构造器)。

  • 类型构造器必须是 private 的,但不能显示标记,只能由编译器来标记为 private .

  • 类型构造器必须无参。

  • CLR保证一个类型构造器在每个AppDomain中只执行一次,而且(这种执行)是线程安全的。 所以非常适合在类型构造器中初始化类型需要的任何单实例(Singleton)对象。

8.4 操作符重载方法

  • CLR规范要求操作符重载方法必须是 publicstatic 方法。

  • C#(以及其他许多语言)要求操作符重载方法至少有一个参数的类型与当前定义这个方法的类型相同。

    public sealed class Complex
{
//重载“+”操作符
public static Complex operator +(Complex c1, Complex c2){...}
}
  • 使用不支持操作符重载的编程语言时,语言应该允许你直接调用希望的 op_* 方法(例如 op_Addition)。反之,如果在C#中引用了不支持操作符重载的语言所写的类型,且类型中提供了一个 op_Addition 方法,这时依然不能使用+操作符来调用这个 op_Addition 方法,因为元数据中没有关联 specialname 标记。

8.5 转换操作符方法

  • CLR要求操作符重载方法必须是 publicstatic 方法。

  • C#(以及其他许多语言)要求参数类型和返回类型二者必有其一与定义转换方法的类型相同。

  • 类型转换模板代码:

    public sealed class Rational {
//由一个Int32构造一个Rational
public Rational(Int32 num) { ...} //由一个Single构造一个Rational
public Rational(Single num) { ...} //将一个Rational转换成一个Int32
public Int32 ToInt32() { ...} //将一个Rational转换成一个Single
public Single ToSingle() { ...} //由一个Int32隐式构造并返回一个Rational
public static implicit operator Rational(Int32 num) {
return new Rational(num);
} //由一个Single隐式构造并返回一个Rational
public static implicit operator Rational(Single num) {
return new Rational(num);
} //由一个Rational显示返回一个Int32
public static explicit operator Int32(Rational r) {
return r.ToInt32();
} //由一个Rational显示返回一个Single
public static explicit operator Single(Rational r) {
return r.ToSingle();
}
} //像前面那样为 Rational 类型定义了转换操作符之后,就可以写出像下面这样的C#代码:
public sealed class Program
{
public static void Main()
{
Rational r1 = 5; //Int32 隐式转型为 Rational
Rational r2 = 2.5F; //Single 隐式转型为Rational Int32 x = (Int32)r1; //Rational 显式转型为Int32
Single s = (Single)r2; //Rational 显式转型为Single
}
}
  • implicit 关键字告诉编译器可以隐式转换; explicit 表示必须显式转换。

  • implicitexplicit 关键字之后,要指定 operator 关键字告诉编译器该方法是一个转换操作符。

  • operator 之后,指定对象要转换成什么为型。在圆括号内,则指定要从什么类型转换。

  • 不损失精度时用 implicit ,否则用 explicit .显式转换失败,应该抛出 OverflowException 或者 InvalidOperationException 异常。

8.6 扩展方法

翠花,上代码:

    //静态的类
public static class StringBuilderExtensions {
//静态方法,this 关键字
public static Int32 IndexOf(this StringBuilder sb, Char value) {
for (Int32 index = 0; index < sb.Length; index++)
if (sb[index] == value) return index;
return -1;
}
}

现在,就可以像这样使用Int32 index=sb.IndexOf('!')

8.6.1 规则和原则

  • C#只支持扩展方法,不支持扩展属性、扩展事件、扩展操作符等。

  • 扩展方法(第一个参数前面有 this 的方法)必须在非泛型的静态类中声明。类名没有限制。至少要有一个参数,而且只有第一个参数能用 this 关键字标记。

  • 如果有人在 Wintellect 命名空间中定义了一个 StringBuilderExtensions 类,那么程序员为了访问这个类的扩展方法,必须在他的源代码文件顶部写一条 using Wintellect; 指令。

  • 扩展方法可能存在版本控制问题。如果 Microsoft 未来为他们的 StringBuilder 类添加了 IndexOf 实例方法,而且和我的代码调用的原型一样,那么在重新编译我的代码时,编译器会绑定到Microsoft的IndexOf实例方法,而不是我的静态IndexOf方法。这样我的程序就会有不同的行为。

8.6.2 用扩展方法扩展各种类型

  • 为接口定义扩展方法
        public static void ShowItems<T>(this IEnumerable<T> collection) {
foreach (var item in collection)
Console.WriteLine(item);
} public static void Main()
{
//每个Char在控制台上单独显示一行
"Grant".ShowItems(); //每个String在控制台上单独显示一行
new[] { "Jeff", "Kristin" }.ShowItems(); //每个Int32在控制台上单独显示一行
new List<Int32>() { 1, 2, 3 }.ShowItems(); }
  • 为委托类型定义扩展方法
        public static void InvokeAndCatch<TException>(this Action<object> d, object o)
where TException : Exception
{
try { d(o); }
catch (TException) { }
} public static void Main()
{
//创建一个Action委托(实例)来引用静态 ShowItems 扩展方法。
//并初始化第一个实参来引用字符串“Jeff”
Action a = "Jeff".ShowItems; //调用(Invoke)委托,后者调用(call)ShowItems.
//并向它传递对字符串“Jeff”的引用
a();
}

8.6.3 ExtensionAttribute类

8.7 分部方法

    //工具生成的代码,存储在某个源代码文件中
internal sealed partial class Base
{
private String m_name; //这是分部方法的声明
partial void OnNameChanging(String value); public String Name
{
get { return m_name; }
set
{
OnNameChanging(value.ToUpper()); //通知类要进行更改了
m_name = value; //更改字段
}
}
} //开发人员生成的代码,存储在另一个源代码文件中:
internal sealed partial class Base
{
//这是分部方法的实现,会在m_name更改前调用
partial void OnNameChanging(string value)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentNullException("value");
}
}

规则和原则:

  • 它们只能在分部类或结构中声明。

  • 分部方法的返回类型始终是 void ,任何参数都不能用 out 修饰符来标记,之所以如此要求,都是因为——方法可能不存在

  • 可以有 ref 参数,可以是泛型方法,可以是实例或静态方法,而且可标记为 unsafe

  • 分部方法的声明和实现必须具有完全一致的签名。

  • 如果没有对应的实现部分,便不能在代码中创建一个委托来引用这个分部方法(编译器会报错)。同样是因为——方法可能不存在

  • 分部方法总是被视为 private 方法,但C#编译器禁止在分部方法声明之前添加 private 关键字。

返回目录

<NET CLR via c# 第4版>笔记 第8章 方法的更多相关文章

  1. <NET CLR via c# 第4版>笔记 第19章 可空值类型

    System.Nullable<T> 是结构. 19.1 C# 对可空值类型的支持 C# 允许用问号表示法来声明可空值类型,如: Int32? x = 5; Int32? y = null ...

  2. <NET CLR via c# 第4版>笔记 第18章 定制特性

    18.1 使用定制特性 FCL 中的几个常用定制特性. DllImport 特性应用于方法,告诉 CLR 该方法的实现位于指定 DLL 的非托管代码中. Serializable 特性应用于类型,告诉 ...

  3. <NET CLR via c# 第4版>笔记 第17章 委托

    17.1 初识委托 .net 通过委托来提供回调函数机制. 委托确保回调方法是类型安全的. 委托允许顺序调用多个方法. 17.2 用委托回调静态方法 将方法绑定到委托时,C# 和 CLR 都允许引用类 ...

  4. <NET CLR via c# 第4版>笔记 第16章 数组

    //创建一个一维数组 int[] myIntegers; //声明一个数组引用 myIntegers = new int[100]; //创建含有100个int的数组 //创建一个二维数组 doubl ...

  5. <NET CLR via c# 第4版>笔记 第13章 接口

    13.1 类和接口继承 13.2 定义接口 C#用 interface 关键字定义接口.接口中可定义方法,事件,无参属性和有参属性(C#的索引器),但不能定义任何构造器方法,也不能定义任何实例字段. ...

  6. <NET CLR via c# 第4版>笔记 第12章 泛型

    泛型优势: 源代码保护 使用泛型算法的开发人员不需要访问算法的源代码.(使用c++模板的泛型技术,算法的源代码必须提供给使用算法的用户) 类型安全 向List<DateTime>实例添加一 ...

  7. <NET CLR via c# 第4版>笔记 第5章 基元类型、引用类型和值类型

    5.1 编程语言的基元类型 c#不管在什么操作系统上运行,int始终映射到System.Int32; long始终映射到System.Int64 可以通过checked/unchecked操作符/语句 ...

  8. <NET CLR via c# 第4版>笔记 第6章 类型和成员基础

    6.1 类型的各种成员 6.2 类型的可见性 public 全部可见 internal 程序集内可见(如忽略,默认为internal) 可通过设定友元程序集,允许其它程序集访问该程序集中的所有inte ...

  9. <NET CLR via c# 第4版>笔记 第7章 常量和字段

    7.1 常量 常量 是值从不变化的符号.定义常量符号时,它的值必须能够在编译时确定. 只能定义编译器识别的基元类型的常量,如果是非基元类型,需把值设为null. 常量的值直接嵌入代码,所以不能获取常量 ...

随机推荐

  1. python控制台输出颜色

    python_控制台输出带颜色的文字方法在开发项目过程中,为了方便调试代码,经常会向stdout中输出一些日志,默认的这些日志就直接显示在了终端中.而一般的应用服务器,第三方库,甚至服务器的一些通告也 ...

  2. git-【三】理解工作区与暂存区的区别

    基本概念 工作区:就是你在电脑上看到的目录,比如目录下testgit里的文件(.git隐藏目录版本库除外).或者以后需要再新建的目录文件等等都属于工作区范畴.       版本库(Repository ...

  3. 概率图模型PFM——无向图

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAdYAAAFPCAIAAAB/EXiGAAAgAElEQVR4nO2df4wl1XXn6/+0VuG/II ...

  4. C#检查文件是否被占用

    第一种方法: using System.IO; using System.Runtime.InteropServices; [DllImport("kernel32.dll")] ...

  5. 3.2 Templates -- The Application Template

    1. 当你的应用程序启动时application模板是默认被渲染的的模板. 2. 你应该把你的header, footer和其他任何的装饰内容放到这里.此外,你应该有至少一个{{outlet}}:它是 ...

  6. Python中的MySQL接口:PyMySQL & MySQLdb

    MySQLdb模块只支持MySQL-3.23到5.5之间的版本,只支持Python-2.4到2.7之间的版本 PyMySQL支持 Python3.0以后的版本 PyMySQL https://pypi ...

  7. Git冲突:commit your changes or stash them before you can merge. 解决办法

    用git pull来更新代码的时候,遇到了下面的问题: 1 2 3 4 error: Your local changes to the following files would be overwr ...

  8. selenium 中装饰器作用

    前面讲到unittest里面setUp可以在每次执行用例前执行,这样有效的减少了代码量,但是有个弊端,比如打开浏览器操作,每次执行用例时候都会重新打开,这样就会浪费很多时间.于是就想是不是可以只打开一 ...

  9. poj3318 Matrix Multiplication

    poj3318 Matrix Multiplication 题意:给定$n*n(n<=500)$的矩阵$A,B,C$,如果$A*B==C$,输出“YES”,否则为“NO”:多组数据,$O(n^{ ...

  10. Jquery的深度拷贝和深度克隆

    有人问,拷贝和克隆不都是“复制”的意思吗. 这位看官问的好,一般情况下是一样的,但在jquery中却有些不同.jqurey深度拷贝一般只js对象的复制,是$.extend()方法,jquery深度克隆 ...