读书笔记: C# 7.0 in a nutshell (第 三 章 Creating Types in C#)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_30162859/article/details/82693515

内容:

第三章: C#中创建类型

  1. 继承
  2. object类型
  3. struct
  4. 访问修饰符
  5. 接口
  6. 枚举
  7. 嵌套类
  8. 泛型

1. Classes

典型的class定义:

class YourClassName
{ }

除此之外:

位置 内容
class前 Attributes and class modifiers, 非嵌套类modifiers包括:
public,internal, abstract, sealed, static, unsafe, partial
类名之后 泛型类,基类,接口
大括号内 类成员,包括:
methods, properties, indexers, events, fields, constructors,overloaded operators, nested types, and a finalizer

1.1 字段

a.字段修饰符:

名称 修饰符
Static modifier static
Access modifiers public internal private protected
Inheritance modifier new
Unsafe code modifier unsafe
Read-only modifier readonly
Threading modifier volatile

b.readonly修饰符

static readonly int legs = 8, eyes = 2;   // 使用逗号定义多个

1.2 方法

a.方法修饰符:

名称 修饰符
Static modifier static
Access modifiers public internal private protected
Inheritance modifier new virtual abstract override sealed
Partial method modifier partial
Unmanaged code modifiers unsafe extern
Asynchronous code modifier async

b.Expression-bodied method

当只包含一条语句的时候:

int Foo (int x) => x * 2;
void Foo (int x) => Console.WriteLine (x);

c.方法重载

函数签名:函数名以及它对应的参数类型顺序(参数名不算, params也不算, 但是ref/out算)

当函数签名不同的时候,就可以重载; 重载中,参数是值传递还是引用传递也可以重载,比如:
Foo(int)可以和Foo(ref int)或者Foo(out int)共存,但是 Foo(ref int)Foo(out int)不可以共存。

d.local method

局部 函数只能被嵌套的函数看到。局部函数可以出现在 setter, 构造器, lambda表达式中。

不能使用static修饰局部方法,如果外部方法是static的,那么局部函数就会自动成为static

void WriteCubes()
{
Console.WriteLine (Cube (3));
Console.WriteLine (Cube (4));
Console.WriteLine (Cube (5));
int Cube (int value) => value * value * value;
}

1.3 构造器

class或者struct都有构造器。

a.构造器的修饰符:

名称 修饰符
Access Modifiers public internal private protected
unmanged code modifiers unsafe extern



从C#7.0开始,构造器也可以使用 expression-bodied:

public Panda (string n) => name = n;

b.调用其他构造器

this关键字和base关键字。被调用的会先执行

public class Wine{
    public Wine (decimal price) { Price = price; }
    public Wine (decimal price, int year) : this (price) { Year = year; }   //被调用的会先执行
}

传入构造器的参数可以是表达式,但是不能调用this,因为此时instance自身都没有创建;不过没有使用static方法。

c.隐式无参public构造器

d.字段初始化顺序

字段的初始化是比构造器初始化更早的,并且按照他们在class中的定义顺序

e.非public构造器

1.4 Deconstructor(C# 7.0)

把一个对象的结果返回给多个数据。Deconstructor也可以重载

class Rectangle
{
    public readonly float Width, Height;

    public Rectangle (float width, float height)
    {
        Width = width;
        Height = height;
    }
    ///deconstructor
    public void Deconstruct (out float width, out float height)
    {
        width = Width;
        height = Height;
    }
}
var rect = new Rectangle (3, 4);
(float width, float height) = rect; // Deconstruction
相当于:
float width, height;
rect.Deconstruct (out width, out height);

1.5 Object Initializers

Bunny b1 = new Bunny { Name="Bo", LikesCarrots=true, LikesHumans=false };
//相当于
Bunny temp1 = new Bunny();      // temp1 is a compiler-generated name
temp1.Name = "Bo";
temp1.LikesCarrots = true;
temp1.LikesHumans = false;
Bunny b1 = temp1;

编译产生了一个临时变量,这就保证初始化中如果出现错误,不会存在一个初始化了一半的对象。

1.6 this

1.7 Properties

a.修饰符

名称 修饰符
Static modifier static
Access modifiers public internal private protected
Inheritance modifiers new virtual abstract override sealed
Unmanaged code modifiers unsafe extern

b.read-only property

c. expression-bodied properties

decimal currentPrice, sharesOwned;

public decimal Worth => currentPrice * sharesOwned;   //get
public decimal Worth
{
    get => currentPrice * sharesOwned;
    set => sharesOwned = value / currentPrice;
}

d.自动属性

编译器自动会产生一个我们无法访问到的字段。

public class Stock
{
    ...
    public decimal CurrentPrice { get; set; }   //自动属性
}

e.初始化属性

public int Maximum { get; } = 999;

f.get和set的访问修饰符

g.CLR属性实现

CLR在compile的时候,把属性编译成get_XXX and set_XXX。 对于很简单的属性,JIT把它直接转化成了inline 语句,而不是方法,使其和直接访问filed的性能相当。

1.8 Indexers

索引也可以使用 ?[]

a.实现indexer

自己定义一个this属性,后面加上[]表示访问方式。indexer也可以有多个,只要访问方式不同。

class Person{

        private string a;
        public string this[int word]
        {
            get => "";
            set => a = value;
        }
        public string this [int arg1, string arg2]
        {
            get { ... } 
            set { ... }
        }
}

b.CLR实现indexer

clr将indexer编译成 get_itemset_item

public string get_Item (int wordNum) {...}
public void set_Item (int wordNum, string value) {...}

1.9 Constants

常数是编译的时候就被替换了,可以是这些类型类型: built-in的numeric,bool,char,enum

public const string Message = "Hello World";

conststatic readonly更加严格,在编译的时候i就已经计算出结果然后进行替换了。只在绝对不会更改数据的情况下使用const

1.10 Static Constructor

一个类型只能定义一个静态构造器,且不包含参数。Runtime在这个类被使用之前自动调用静态构造方法,包括:

1. 初始化这个类型
2. 访问这个类型的静态成员

只有unsafeextern修饰符能修饰静态构造器。

class Test
{
    static Test() { Console.WriteLine ("Type Initialized"); }
}

a. 和属性初始化的顺序

先初始化静态成员,再初始化静态构造器。

class Foo
{
    public static Foo Instance = new Foo();   //这里先调用构造器,再执行下一句
    public static int X = 3;
    Foo() { Console.WriteLine (X); } // 0
}
//如果把上面2个static 成员换位置,那么打印出来的就是3了

1.11 静态类

static修饰的类,只能由静态成员组成,且不能被继承。如System.ConsoleSystem.Math

1.12 Finalizer

垃圾回收之前执行的, 实际上只override了object类的Finalizer方法。

class Class1
{
    ~Class1()   //finalizer
    {
    ...
    }
}

1.13 Partial Class

每个定义的地方都要标记成partial,他们之间不能有冲突的成员,可以标注父类,只要相同就行。

他们必须要在同一个assembly中,编译的时候由编译器处理。但是编译器不保证字段的初始化顺序。

// PaymentFormGen.cs - auto-generated
partial class PaymentForm { ... }
// PaymentForm.cs - hand-authored
partial class PaymentForm { ... }

a. partial method

  1. 2个部分,一个方法定义,一个方法实现。
  2. 如果没有实现部分,则编译的时候这个方法自动会被去除
  3. partial method必须返回void,且本身是 private
partial class PaymentForm // In auto-generated file
{
    ...
    partial void ValidatePayment (decimal amount);    //部分方法
}
partial class PaymentForm // In hand-authored file
{
    ...
    partial void ValidatePayment (decimal amount)    //实现方法的,当作hook使用
    {
        if (amount > 100)
        ...
    }
}

1.14 nameof

string name = nameof (StringBuilder.Length);   //返回Length
nameof (StringBuilder) + "." + nameof (StringBuilder.Length);   //返回 StringBuilder.Length

2. Interitance

2.1 多态polymorphism

2.2 转换

    //向上转
    Stock msft = new Stock();
    Asset a = msft; // Upcast
    Console.WriteLine (a == msft); // True
    //向下转
    Stock msft = new Stock();
    Asset a = msft; // Upcast
    Stock s = (Stock)a; // Downcast
    Console.WriteLine (s.SharesOwned); // <No error>
    Console.WriteLine (s == a); // True
    Console.WriteLine (s == msft); // True

a. as运算符

向下转,转换失败返回null。 as操作符对数字类型转换以及自定义转换没用。

Stock s = a as Stock; // s is null; no exception thrown

b. is操作符

判断一个对象是否是一个类的子类。 is操作符对数字类型转换以及自定义转换没用。

// pattern variable
if (a is Stock s)
Console.WriteLine (s.SharesOwned);

2.3 Virtual Function Members

方法、属性、indexer、event可以标记为virtual,而被子类重写。

public class Asset
{
    public string Name;
    public virtual decimal Liability => 0; // Expression-bodied property
}
public class House : Asset
{
    public decimal Mortgage;
    public override decimal Liability => Mortgage;
}

签名、返回值、访问修饰符都需要一样。

2.4 抽象类和抽象方法

抽象类无法被 初始化,只有它的实体子类可以。抽象类可以有abstract子类成员。

2.5 Hiding Inherited Members

当基类和子类都有同名成员的时候,编译的时候会出警告。

public class A { public int Counter = 1; }   //A.Account
public class B : A { public int Counter = 2; }   //B.Account

使用new修饰符,会明确的掩盖父类同名成员,让编译器不报警告:

public class A { public int Counter = 1; }
public class B : A { public new int Counter = 2; }

a. new和override的区别:

public class BaseClass
{
    public virtual void Foo() { Console.WriteLine ("BaseClass.Foo"); }
}
public class Overrider : BaseClass
{
    public override void Foo() { Console.WriteLine ("Overrider.Foo"); }
}
public class Hider : BaseClass
{
    public new void Foo() { Console.WriteLine ("Hider.Foo"); }
}

Overrider over = new Overrider();
BaseClass b1 = over;
over.Foo(); // Overrider.Foo
b1.Foo(); // Overrider.Foo
Hider h = new Hider();
BaseClass b2 = h;
h.Foo(); // Hider.Foo
b2.Foo(); // BaseClass.Foo

2.6 Sealing function和class

当子类重写了父类的方法时,有时不希望这些方法继续被他们的子类重写,所以这时候使用sealed来禁止重写效果。

public sealed override decimal Liability { get { return Mortgage; } }

也可以对这个类使用sealed来封锁这个类中所有的重写结果。

2.7 base关键字

base的作用:

  1. 在重写方法中访问被重写的父类方法(hidden的也适用)
  2. 调用父类的构造器

2.8 构造器的继承

子类可以访问父类的一些构造器,但是不是继承。

    public Subclass (int x) : base (x) { }   //父类的先调用

a. 自动调用父类无参构造器

如果子类构造器中没有使用base,则会自动调用父类的无参构造器。

b. 对象初始化顺序

  1. 初始化自己的字段
  2. 计算调用的父类构造器的参数
  3. 父类初始化它的字段
  4. 父类执行构造器
  5. 子类执行自己的构造器

2.9 解决方法重构的调用

重构方法会先调用类型最匹配的那个,这个是编译时决定的。

//2个方法
static void Foo (Asset a) { }
static void Foo (House h) { }

House h = new House (...);
Foo(h); // Calls Foo(House)
Asset a = new House (...);
Foo(a); // Calls Foo(Asset)

3. The object type

当在值类型和object类型之间转换的时候,CLR要进行一些操作,来让这两种的用法不同。这叫做boxingunboxing

3.1 装箱拆箱

将值类型转换成引用类型叫装箱,引用类型可以是 object或者接口

int x = 9;
object obj = x; // Box the int
int y = (int)obj; // Unbox the int

object obj = 9; // 9 is 被推断为int类型
long x = (long) obj; // InvalidCastException

object obj = 9;
long x = (int) obj;   //成功
object obj = 3.5; // 3.5 is inferred to be of type double
int x = (int) (double) obj; // x is now 3

object[] a1 = new string[3]; // Legal,引用类型可以转换
object[] a2 = new int[3]; // Error  不能一起类型转换装箱

3.2 Static&Runtime Type checking

强制类型转换是在 Runtime,由CLR进行的,CLR通过获取 heap上对象的metadata来查看,比如说里面保存的 Type类型。

3.3 GetType方法和 typeof 操作符

所有C#类,都对应了一个System.Type类型,获取方式:

  1. 对象.GetType() //运行时
  2. typeof 类型名 // 编译时

3.4 ToString()

注意,直接对值类型调用object的方法,不会发生boxing:

string s1 = x.ToString(); // Calling on nonboxed value

3.4 object对象的成员

public class Object
{
public Object();
public extern Type GetType();
public virtual bool Equals (object obj);
public static bool Equals (object objA, object objB);
public static bool ReferenceEquals (object objA, object objB);
public virtual int GetHashCode();
public virtual string ToString();
protected virtual void Finalize();
protected extern object MemberwiseClone();
}

4. Structs

和class的区别:

  1. 值类型
  2. 不支持继承
  3. 不能有无参构造器
  4. 不能有 字段初始化
  5. 不能有 finalizer
  6. 不能有 virtual或者protected的成员

4.1 构造器

  1. 默认有一个无法override的隐式无参构造器, 一直存在
  2. 当自己定义构造器的时候,必须初始化每个字段(无参构造器一直存在)

5. Access modifiers

修饰符 作用
public 公开访问; 是 enuminterface成员的默认修饰符
internal 只对containing assembly or friend assemblies 公开,是非嵌套类的默认修饰符
private 只对类内部可见,是 classstruct成员默认修饰符
protected 对类内部和子类可见
protected internal 上2个的组合

5.1 Friend Assembly

internal成员添加别的可以暴露的assembly

5.2 Accessibility Capping

类的访问修饰符,直接限制了成员可访问性的上限。

class C { public void Foo() {} }   //C是internal,所以Foo虽然标注public,但实际只能是internal

5.3 Restrictions on Access Modifiers

当重写父类方法的时候,重写方法的可访问性只能 小于等于 原方法的可访问性

6. Interfaces

  1. interface的所有成员是 abstract的, 需要被继承的 class或者struct来重写
  2. interface的成员都是public的,不能添加任何修饰符
  3. 实现类的成员,比如是internal的,那么将它转换成接口,还是能以public调用
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}

6.1 显式接口

使用接口名.方法名来实现接口的方法。要调用这个方法,唯一的办法就是将其先转换成接口再调用

public class Widget : I1, I2
{
    public void Foo()
    {
        Console.WriteLine ("Widget's implementation of I1.Foo");
    }
    //方法使用接口名.方法名
    int I2.Foo()  
    {
        Console.WriteLine ("Widget's implementation of I2.Foo");
        return 42;
    }
}

Widget w = new Widget();
w.Foo(); // Widget's implementation of I1.Foo
((I1)w).Foo(); // Widget's implementation of I1.Foo
((I2)w).Foo(); // Widget's implementation of I2.Foo

6.2 virtual实现接口

隐式实现的接口成员默认是sealed,如果要他们能被重写,需要标注virtual或者abstract

public interface IUndoable { void Undo(); }
public class TextBox : IUndoable
{
    public virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}

public class RichTextBox : TextBox
{
    public override void Undo() => Console.WriteLine ("RichTextBox.Undo");
}

An explicitly implemented interface member cannot be marked virtual, nor can it be overridden in the usual manner. It can, however, be reimplemented.

6.3 子类中重新实现接口

子类可以重新实现一次接口,就算它被父类实现过一次了。

public interface IUndoable { void Undo(); }
public class TextBox : IUndoable
{
    //这是 explicityly实现的
    void IUndoable.Undo() => Console.WriteLine ("TextBox.Undo");
}
public class RichTextBox : TextBox, IUndoable     //显式表明接口,再重新实现一次
{
    public void Undo() => Console.WriteLine ("RichTextBox.Undo");
}

RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo Case 1
((IUndoable)r).Undo(); // RichTextBox.Undo Case 2

//当baseclass是隐式实现接口的时候,这有时就会导致一些没期望的结果
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo Case 1
((IUndoable)r).Undo(); // RichTextBox.Undo Case 2
((TextBox)r).Undo(); // TextBox.Undo Case 3

这种重新实现接口的方式容易造成问题,特别对于父类来说,它可能并没有希望子类重新实现接口。如果真有这么个需求的话,那么要么父类使用 implicitly实现,要么使用模板方法

6.4 Interfaces and Boxing

把结构体转成interface,会造成一次装箱

7. Enums

public enum BorderSide { Left, Right, Top, Bottom }

一种特殊的值类型,默认底层是整形:

  1. 底层是int类型
  2. 默认赋值为0,1,2…

可以自己改变类型和值:

public enum BorderSide : byte { Left, Right, Top, Bottom }
public enum BorderSide : byte { Left=1, Right=2, Top=10, Bottom=11 }  //没有指定的会依次增加

7.1 转换

对于enum来说,0是一个特殊值,不需要转换,默认代表定义的第一个值

int i = (int) BorderSide.Left;     //转换成底层int
BorderSide side = (BorderSide) i;   //从int转换

HorizontalAlignment h = (HorizontalAlignment) BorderSide.Right;   //按照值相等,转换成另一个enum
HorizontalAlignment h = (HorizontalAlignment) (int) BorderSide.Right;

BorderSide b = 0; // No cast required

7.2 Flags Enum

可以组合Enum的值,为了避免歧义,成员一般都要赋值,且一般是2的指数:

[Flags]
public enum BorderSides { None=0, Left=1, Right=2, Top=4, Bottom=8 }

BorderSides leftRight = BorderSides.Left | BorderSides.Right;


// 定义的时候直接定义组合enum
[Flags]
public enum BorderSides
{
    None=0,
    Left=1, Right=2, Top=4, Bottom=8,
    LeftRight = Left | Right,
    TopBottom = Top | Bottom,
    All = LeftRight | TopBottom
}

7.3 运算符

= == != < > <= >= + - ^ & | ˜
+= -= ++ -- sizeof

enum可以和整数加法,但是不能2个enum相加。

通常组合enum的Flag特性一定要定义;组合enum的名称一般是复数形式。

7.4 检查正确性

Enum.IsDefined   //检查enum的值是否是正确定义的值中间的,但是对 组合enum 不起作用

8. Nested Types

特点:

  1. 可以访问包含类的所有成员,以及其他包含类能访问到的对象
  2. 可以使用所有modifier,不仅仅是public和internal
  3. 默认的可访问性是private
  4. 访问嵌套类,需要加上外部类的quanlified name
TopLevel.Color color = TopLevel.Color.Red;

9. Generics

好处: 增加代码重用,提高代码安全,减少转换和装箱

public class Stack<T>
{
int position;
T[] data = new T[100];
public void Push (T obj) => data[position++] = obj;
public T Pop() => data[--position];
}

在runtime,所有的 open type都成为了 closed type

9.1 为什么存在Generics

9.2 Generic 方法

static void Swap<T> (ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}

只有方法和类能够加入 type 参数,属性、indexer、events、字段、构造器等等,都只能利用已经引入的type 参数。

9.3 声明类型参数

classes, structs, interfaces, delegates能够在定义的时候,使用type parameter.

public struct Nullable<T>
{
public T Value { get; }
}

9.4 typeof 和 unbounded generic type

在runtime的时候,由于所有open type都已经替换成了close type, 所以在其之前,只有使用 typeof 能够获取 这些unbounded的Type类型,可以用于反射。

Type a1 = typeof (A<>); // Unbound type (notice no type arguments).
Type a2 = typeof (A<,>); // Use commas to indicate multiple type args.

9.5 默认generic value

使用default可以帮助使用默认值。

9.6 Generic Constraints

where T : base-class // Base-class constraint
where T : interface // Interface constraint
where T : class // Reference-type constraint  引用类型
where T : struct // Value-type constraint (excludes Nullable types) 值类型
where T : new() // Parameterless constructor constraint
where U : T // Naked type constraint

class GenericClass<T,U> where T : SomeClass, Interface1
                        where U : new()

9.7 泛型子类

子类可以留着泛型参数, 也可以close, 也可以引入新泛型

class Stack<T> {...}
class SpecialStack<T> : Stack<T> {...}

class IntStack : Stack<int> {...}

class List<T> {...}
class KeyedList<T,TKey> : List<T> {...}

9.8 泛型引用自己

public class Balloon : IEquatable<Balloon>

9.9 静态数据

每一个closed type都有自己的一份静态数据。

9.10 泛型中转换类型

使用as或者先装箱再拆箱

9.11 Covariance Contravariance

从C# 4.0开始, 接口、deletegate、array都允许 covariant type parameter,但是class不行。

class Animal {}
class Bear : Animal {}
class Camel : Animal {}
public class Stack<T> // A simple Stack implementation
{
    int position;
    T[] data = new T[100];
    public void Push (T obj) => data[position++] = obj;
    public T Pop() => data[--position];
}

以下代码无法编译:

Stack<Bear> bears = new Stack<Bear>();
Stack<Animal> animals = bears; // Compile-time error

解释: 对于 bears来说,它本身内部是 Bears[] data = new Bear[100],如果上面语句成功了, 所以编译器担心会有 animals.Push (new Camel());的情况发生,而Camel无法放入 Bear[]

Covariance

 Covariance : 假设A能转换成B,那么 X有一个 covariant type 参数如果 X<A> 也能转换成 X<B>

.使用out,代表这个类型仅出现在 输出位置,即当输出Animal的时候,输出Bear也是可以的。

public interface IPoppable<out T> { T Pop(); }   //out代表这个类型,仅用作输出位置
var bears = new Stack<Bear>();
IPoppable<Animal> animals = bears; // Legal

Contravariance

 Contravariance : 假设A能转换成B,那么 X有一个 contravariant type 参数如果 X<B> 也能转换成 X<A>

以下in代表这个类仅在输入位置,即输入 Bear的时候,底层是Animal[]接收,所以可以。

public interface IPushable<in T> { void Push (T obj); }

IPushable<Animal> animals = new Stack<Animal>();
IPushable<Bear> bears = animals; // Legal
bears.Push (new Bear());

9.12 C#泛型 vs. C++ templates

C# 中,closed type是在runtime确定的,所以在编译的时候,open type编译成了library (dll) ; 而C++来说,它在编译时就确定了这些类的源代码,这样也让运行时的动态检查特别麻烦。

C#中下面的例子无法编译,因为 +并不能使用与所有类型

static T Max <T> (T a, T b)
=> (a > b ? a : b);

C++中,下面的代码对所有能使用+的类型都编译了一遍

template <class T> T Max (T a, T b)
{
return a > b ? a : b;
}

猜你喜欢

转载自blog.csdn.net/qq_30162859/article/details/82693515