读书笔记: C# 7.0 in a nutshell (第 四 章 Advanced C#- 上)

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

内容:

第四章(上): Advanced C#

  1. Delegate
  2. Events
  3. Lamda
  4. Anonymous Methods
  5. Exception Handler
  6. Enumeration & Iterartor
  7. Nullable Types
  8. Extension Methods
  9. Anonymous Types

1. Delegate

一个delegate type定义了 delegate 实例可以调用的方法类型。

例子:

// 这里定义了一个delegate类型,规定输入输出的参数类型
delegate int Transformer (int x);

class Test
{
    static void Main()
    {
        //Transformer t = new Transformer (Square);
        Transformer t = Square; // Create delegate instance
        //t.Invoke(3)
        int result = t(3); // Invoke delegate
        Console.WriteLine (result); // 9
    }

    static int Square (int x) => x * x;
}

1.1 Writing Plug-in Methods with Delegates

就是把委托当做函数的参数, 这个函数就叫做高阶函数: high-order function

1.2 Multicast Delegates☆

每个delegate instance可以引用不止一个target method:

SomeDelegate d = SomeMethod1;
d += SomeMethod2;

注意

  1. 调用delegate instance的顺序,会和 添加引用顺序一致;
  2. delegate 是不可变的,当使用 +=-=的时候,实际上是创建了一个新的delegate instance,再给他赋值
  3. 如果被代理的method有返回值,那么调用delegate得到的返回值为最后执行的method的返回值
  4. 所有delegate继承自System.MulticastDelegate,再继承自System.Delegate
  5. 使用+=-=实际上会被编译成System.Delegate的静态方法:CombineRemove

1.3 实例目标方法 vs. 静态目标方法

当一个delegate instance 引用了某个实例 target method的时, System.Delegate类的Target属性维护了这个方法的实例对象, Method属性指向了这个方法。

class Test
{
    static void Main()
    {
        X x = new X();
        ProgressReporter p = x.InstanceProgress;
        p(99); // 99
        Console.WriteLine (p.Target == x); // True
        Console.WriteLine (p.Method); // Void InstanceProgress(Int32)
    }
}
class X
{
public void InstanceProgress (int percentComplete)  => Console.WriteLine (percentComplete);
}

1.4 泛型委托

定义委托类型的时候可以使用泛型

public delegate T Transformer<T> (T arg);

1.5 Func & Action

除了 ref/outpointer 参数的 delegate,其他基本都可以用这两个替换

1.6 Delegate vs. Interface

用delegate解决的地方也能使用接口解决,那么何时使用delegate更好:

  1. 接口本身只定义了一个方法
  2. 需要multicast delegate
  3. subscriber需要多次实现接口

1.7 Delegate兼容性

a. 类型兼容性

delegate类型都彼此不兼容,就算他们 函数签名相同:

delegate void D1();
delegate void D2();
...
D1 d1 = Method1;
D2 d2 = d1; // Compile-time error
// 但是下面这个可以
D2 d2 = new D2 (d1);

Delegate相等的条件是,他们的target methods相等:

D d1 = Method1;
D d2 = Method1;
Console.WriteLine (d1 == d2); // True

Multicast delegates are considered equal if they reference the same methods in the same order.

b. 参数兼容性

delegate的参数具有 contravariance: (比如 传object参数的方法 给 string参数的delegate)

static void ActOnObject (object o) => Console.WriteLine (o); 
...
delegate void StringAction (string s);
StringAction sa = new StringAction (ActOnObject);

c. 返回值兼容性:

delegate的参数具有 covariance: (比如 传string返回值的方法 给 object返回值的delegate)

static string RetrieveString() => "hello";
...
delegate object ObjectRetriever();
ObjectRetriever o = new ObjectRetriever (RetrieveString);

d. 泛型delegate使用variance

如果定义泛型的delegate,好的习惯是:

  1. 把作为返回值的type parameter表示为 out
  2. 把作为参数传入的 type parameter 表示为 int

2. Events

目的:

     使用delegate主要是为了实现broadcaster-subscriber模式。 broadcaster内有一个 delegate instance, subscriber通过向这个delegate instance注册target method,来接收消息;broadcaster来决定何时invoke这个delegate。

Events作用:

event是C#提供的实现这个模式的语法, 它相当于是 delegate的一个子集,只提供了完成 subscriber模式必须的功能。event的存在是为了阻止 subscriber间的相互干扰。

// Delegate definition
public delegate void PriceChangedHandler (decimal oldPrice,
decimal newPrice);
public class Broadcaster
{
    // Event declaration
    public event PriceChangedHandler PriceChanged;
}

在上面的 Broadcaster类中,可以像对待delegate一样对待这个event; 而在这个类的外面,只能使用+=-=

好处☆:

虽然没有event,代码也同样执行,但是event能提供如下的安全性:

  1. 让其他的subscriber没有办法重新赋值 PricedChanged,而只能使用 +=
  2. 没法让event清除所有的subscriber
  3. 没办法从外部调用event

2.1 Event内部怎么工作的

public class Broadcaster
{
    public event PriceChangedHandler PriceChanged;
}

compiler会把上面的,转换成类似下面这样:

PriceChangedHandler priceChanged; // private delegate
public event PriceChangedHandler PriceChanged
{
    add { priceChanged += value; }
    remove { priceChanged -= value; }
}

addremove关键字,就相当于属性的一样,控制访问。别的类使用+=-=的时候,实际上是调用了addremove,且只能使用这两种方式来访问。

2.2 Standard Event Pattern

C#有一个标准的 模式来使用event( 使用 System.EventArgs):

1.定义自己的 EventArgs,继承自 System.EventArgs,在其中保存event需要传递的信息:

public class PriceChangedEventArgs : System.EventArgs
{
    //保存的信息,通常是 只读属性或者read-only field
    public readonly decimal LastPrice;
    public readonly decimal NewPrice;

    //构造器, 在其中保存2个要传递的信息
    public PriceChangedEventArgs (decimal lastPrice, decimal newPrice)
    {
        LastPrice = lastPrice;
        NewPrice = newPrice;
    }
}

2.定义一个delegate event的类型

三个原则:
    1. 返回值为void
    2. 接收2个参数,第一个是object,代表event broadcaster对象; 第二个参数是 EventArgs的子类,代表消息
    3. 名字必须以`EventHandler`结尾

推荐: 使用框架定义的泛型类: System.EventHandler<>来创建:

public delegate void EventHandler<TEventArgs>
    (object source, TEventArgs e) where TEventArgs : EventArgs;

在有泛型以前,只能自定义一个 EventHandler类型;因为历史原因,所以现在很多见到的EventHandler都是以前自定义的

3.定义event实例:

public class Stock
{
    ...
    public event EventHandler<PriceChangedEventArgs> PriceChanged;
}

4.创建 protected virtual方法,在其中fire event:

创建的这个函数名必须以On开头,之后加上这个Event Instance的名字

public class Stock
{
    ...
    public event EventHandler<PriceChangedEventArgs> PriceChanged;

    protected virtual void OnPriceChanged (PriceChangedEventArgs e)
    {
        if (PriceChanged != null) PriceChanged (this, e);
    }
}

5.对于多线程安全的情况,需要把delegate给一个临时变量,使用这个临时变量来invoke delegate:

var temp = PriceChanged;
if (temp != null) temp (this, e);

6.对于不需要传递信息的EventArgs,可以直接使用EventArgs.Empty来返回一个实例:

protected virtual void OnPriceChanged (EventArgs e)
{
    PriceChanged?.Invoke (this, e);
}

2.3 Event Accessors

普通的定义一个event:

public event EventHandler PriceChanged;

会被编译器处理为:

  1. 一个私有的delegate field
  2. 一组公开的 event accessor functions:add_PriceChangedremove_PriceChanged

所以我们可以自定义这么一个event accessor:

private EventHandler priceChanged; // Declare a private delegate
public event EventHandler PriceChanged
{
    add { priceChanged += value; }
    remove { priceChanged -= value; }
}

但是C#自身会使用compare-and-swap算法来保证更新delegate时候的线程安全。

2.4 Event Modifiers

event也可以使用 virtual,overridden,abstract,sealed,static来修饰

3. Lambda 表达式

lambda表达式是为了替换delegate的无名方法。 编译器会把lambda表达式编译成下面2者之1:

  1. A delegate instance
  2. An expression tree, of type Expression<TDelegate>, representing the code inside the lambda expression in a traversable object model. This allows the lambda expression to be interpreted later at runtime

a. 形式

(parameters) => expression-or-statement-block

lambda表达式的参数和返回值,会根据对应的delgate类型来推断出来。

例子:

Transformer sqr = x => x * x;
Console.WriteLine (sqr(3)); // 9

这种形式的lambda表达式,会被编译器内部编译成一个private 的方法,然后把表达式的内容放到这个方法里面。

3.1. 指定lambda 参数类型

当遇到无法推断类型的情况,需要自己指定参数类型:

Bar ((int x) => Foo (x));

3.2 Capturing Outer Variables☆

lambda表达式可以引用外部的变量:

static void Main()
{
int factor = 2;
Func<int, int> multiplier = n => n * factor;
Console.WriteLine (multiplier (3)); // 6
}

被引用的外部变量叫做 captured variable, 这种情况叫做closure(闭包)

1.captured variable是在delegate被调用的时候,才会计算值:

int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30

2.lambda表达式内部可以改变外部变量的值:

int seed = 0;
Func<int> natural = () => seed++;
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
Console.WriteLine (seed); // 2

3.外部变量的生命周期被延长到了和这个lambda表达式一样长:

static Func<int> Natural()
{
int seed = 0;
return () => seed++; // Returns a closure
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
}

4.在Lambda表达式中声明的新变量,对于每一次调用这个delegate , 这个变量都是一个新的:

static Func<int> Natural()
{
return() => { int seed = 0; return seed++; };
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 0
}

5.lambda内部实际上是把这个captured variablehoisting到了一个private class中,当delegate被调用的时候,会初始化这个 private class,它的生命周期和这个delegate一样。

a. capturing iteration variables

当capture一个在for循环中的变量时,lambda将这个变量当做声明在这个循环之外,所以每一次循环都会capture同一个变量:

Action[] actions = new Action[3];

for (int i = 0; i < 3; i++)
    actions [i] = () => Console.Write (i);

foreach (Action a in actions) a(); // 333

这3个lambda表达式引用的都是同一个i,且i的值是在调用lambda的时候才计算:

Action[] actions = new Action[3];
int i = 0;
actions[0] = () => Console.Write (i);
i = 1;
actions[1] = () => Console.Write (i);
i = 2;
actions[2] = () => Console.Write (i);
i = 3;
foreach (Action a in actions) a(); // 333

如果想要每次的调用结果不一样,需要在lambda内部自己创建局部变量来记录:

Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
int loopScopedi = i;
actions [i] = () => Console.Write (loopScopedi);
}
foreach (Action a in actions) a(); // 012

在C# 5.0以前,foreach 循环也会导致和for循环相同的结果,但是C# 5.0以后修改了,所以:

Action[] actions = new Action[3];
int i = 0;
foreach (char c in "abc")
    actions [i++] = () => Console.Write (c);

foreach (Action a in actions) a(); // ccc in C# 4.0 ; abc in C# 5.0

3.3 Lambda vs. Local method

他们2个的效果是重叠的,但是局部方法有下列三个优势:

  1. 他们自身能递归调用自己
  2. 他们避免了一大堆的delegate 声明
  3. 引入较小的 overhead,没有hoisting

总的来说,局部方法会更加的方便和性能高, 但是delegate会写的更简洁

4. Anonymous Methods

delegate (参数列表) {函数体};

各种性质和lambda一样

5. Try和 Exception

try
{
... // exception may get thrown within execution of this block
}
catch (ExceptionA ex)
{
... // handle exception of type ExceptionA
}
catch (ExceptionB ex)
{
... // handle exception of type ExceptionB
}
finally
{
... // cleanup code
}

5.1 catch 块

从C# 6.0开始,有了exception filter:

catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
...
}

5.2 finally 块

finally块一直会执行,除非内部之前无限循环

对实现了System.IDisposable的使用using

using (StreamReader reader = File.OpenText ("file.txt"))
{
...
}
相当于
{
StreamReader reader = File.OpenText ("file.txt");
try
{
...
}
finally
{
if (reader != null)
((IDisposable)reader).Dispose();
}
}

5.3 抛出异常

throw new ArgumentNullException (nameof (name));

a. C# 7.0 throw作为表达式

public string Foo() => throw new NotImplementedException();
value == null ? throw new ArgumentException ("value") : "";

b. 再次抛出异常

一般是无法处理,所以写个log什么的之后再次抛出;或者再次抛出一个更specific的异常类型:

try { ... }
catch (Exception ex)
{
// Log error
...
    throw; // Rethrow same exception
}

throw new XmlException ("Invalid DateTime", ex);     //ex作为了新异常的 `InnerException`

5.4 Key Properties of System.Exception

属性名 作用
StackTrace A string representing all the methods that are called from the origin of the exception to the catch block
Message A string with a description of the error.
InnerException The inner exception (if any) that caused the outer exception. This, itself, may have another InnerException

5.5 常见异常类型

  • System.ArgumentException
  • System.ArgumentNullException
  • System.ArgumentOutOfRangeException
  • System.InvalidOperationException
  • System.NotSupportedException
  • System.NotImplementedException
  • System.ObjectDisposedException
  • NullReferenceException : throw null;
    ·

5.6 The TryXXX Method Pattern

一个方法内部可能有异常,可以定义2个, 一个是直接抛出异常等待处理,另一个是返回调用结果,让用户之后处理:

可以在普通方法内部调用TryXXX

public return-type XXX (input-type input)
{
    return-type returnValue;
    if (!TryXXX (input, out returnValue))
    throw new YYYException (...)
    return returnValue;
}

6. Enumeration and Iterators

6.1 Enumeration

Enumerator是一个只读只往前走的一系列值Cursor,Enumerator要实现下列接口之一,包含Current属性和MoveNext方法:

  1. System.Collections.IEnumerator
  2. System.Collections.Generic.IEnumerator

Enumerable对象可以被foreach循环访问,它本身并不是cursor,但是会产生遍历自身的cursor,自己表示了一系列数字, 要满足下列条件之一:

  1. 实现 IEnumerable or IEnumerable<T>
  2. 有一个方法叫做GetEnumerator并且返回一个 Enumerator

例子:

class Enumerator // Typically implements IEnumerator or IEnumerator<T>
{
    public IteratorVariableType Current { get {...} }
    public bool MoveNext() {...}
}
class Enumerable // Typically implements IEnumerable or IEnumerable<T>
{
    public Enumerator GetEnumerator() {...}
}


using (var enumerator = "beer".GetEnumerator())    // 之后释放 Enumerator资源
while (enumerator.MoveNext())
{
    var element = enumerator.Current;
    Console.WriteLine (element);
}
等同于:
foreach (char c in "beer"){...}

6.2 Collection Initializers

一个 enumerable 对象,并且有Add方法以及适当的参数:

List<int> list = new List<int> {1, 2, 3};
相当于:
List<int> list = new List<int>();
list.Add (1);
list.Add (2);
list.Add (3);

var dict = new Dictionary<int, string>()
{
{ 5, "five" },
{ 10, "ten" }
};
var dict = new Dictionary<int, string>()
{
[3] = "three",
[10] = "ten"
};

6.3 Iterator

iterator产生enumerator:

static IEnumerable<int> Fibs (int fibCount)
{
    for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
    {
        yield return prevFib;            // 返回下一个enurator得到的值
        int newFib = prevFib+curFib;
        prevFib = curFib;
        curFib = newFib;
    }
}

编译器内部把这个编译成了一个 private class,实现了IEnumerable<T> and/or IEnumerator<T>, 这个类的生命周期是和Enumerator绑定的,意思是当不去遍历它的时候,这个类结束。

6.4 Iterator的语法

  1. iterator是一个包含一条或者多条yield语句的 : 方法、属性 或者 indexer
  2. 一个iterator必须返回下列4个接口之一:
// Enumerable interfaces
System.Collections.IEnumerable
System.Collections.Generic.IEnumerable<T>
// Enumerator interfaces
System.Collections.IEnumerator
System.Collections.Generic.IEnumerator<T>

a. 多条yield return

static IEnumerable<string> Foo()
{
yield return "One";
yield return "Two";
yield return "Three";
}

static void Main()
{
foreach (string s in Foo())
Console.WriteLine(s); // Prints "One","Two","Three"
}

b. yield return

static IEnumerable<string> Foo (bool breakEarly)
{
yield return "One";
yield return "Two";
if (breakEarly)
    yield break;             //提前退出iterator
yield return "Three";
}

c. Iterators and try/catch/finally blocks

yield return 只能放在 try-fianlly的try中,不能放在try-catch,try-catch-finally的其他地方。这是因为编译器必须把iterator转换成一个拥有MoveNext, Current, and Dispose members的普通类,那么还要异常处理的话就会增加这个转换的复杂性。

只有下面这个代码是可行的( try-finally中的try有yield)

IEnumerable<string> Foo()
{
    try { yield return "One"; } // OK
    finally { ... }
}

6.5 组合多个iterator:

using System;
using System.Collections.Generic;
class Test
{
    static void Main()
    {
        foreach (int fib in EvenNumbersOnly (Fibs(6)))
        Console.WriteLine (fib);
    }

    static IEnumerable<int> Fibs (int fibCount)
    {
        for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
        {
            yield return prevFib;
            int newFib = prevFib+curFib;
            prevFib = curFib;
            curFib = newFib;
        }
    }
    static IEnumerable<int> EvenNumbersOnly (IEnumerable<int> sequence)
    {
        foreach (int x in sequence)
        if ((x % 2) == 0)
        yield return x;
    }
}

7. 可空类型

T? 是 System.Nullable<T>的语法糖, 是一个轻量级不可变的结构体(值类型),只有2个字段,ValueHasValue

public struct Nullable<T> where T : struct
{
    public T Value {get;}
    public bool HasValue {get;}
    public T GetValueOrDefault();
    public T GetValueOrDefault (T defaultValue);
    ...
}

7.1. 转换

int? x = 5; // implicit
int y = (int)x; // explicit   相当于调用  .Value

7.2. 拆装箱

When T? is boxed, the boxed value on the heap contains T, not T?

object o = "string";
int? x = o as int?;    // 使用as
Console.WriteLine (x.HasValue); //False

7.3. 符号提升

Nullable<T>本身没有运算操作,但是下面代码可以

int? x = 5;
int? y = 10;
bool b = x < y; // true

因为C#通过符号提升,将这些代码运用到了 底层的数据中使用,相当于:

bool b = (x.HasValue && y.HasValue) ? (x.Value < y.Value) : false;

7.4 相等操作符

符号提升后的==,比较规则是这样的:

  1. 如果都是null,则相等
  2. 只有一个 不是null,则不等
  3. 如果都不是null,则比较Value

7.5 比较操作符

<= >= < >这些操作符,在比较其中一个是Null的时候,都会返回false

7.6 其他操作符

+, −, *, /, %, &, |, ^, <<, >>, +, ++, --, !, ~在有操作数为null时都会返回null

7.7 bool?和 & |操作符

&和|把null当作位置值,所以

null | null  // null
null | False  // null
null | True  // True
null & null  //null
null & False  //False
null & True  //null

7.8 没有Nullable类型以前怎么办:

  1. 特殊值表示null
  2. 包装类+flag

8. Extension Methods

扩展方法是静态方法,第一个参数this表示扩展的类,参数类型就是这个扩展类的类型:

public static class StringHelper
{
    // 扩展方法,扩展类型是 string
    public static bool IsCapitalized (this string s)
    {
        if (string.IsNullOrEmpty(s)) return false;
        return char.IsUpper (s[0]);
    }
}

调用扩展方法,会被编译成一个普通的静态方法

Console.WriteLine ("Perth".IsCapitalized());
//编译成
Console.WriteLine (StringHelper.IsCapitalized ("Perth"));

也可以对接口扩展

8.1 扩展方法的使用

调用静态方法,首先要保证这个定义扩展方法的namespace已经被引入

8.2 调用冲突

  1. 实例方法永远优先于扩展方法, 如果此时要调用扩展方法,只能像调用静态方法一样调用
  2. 当存在2个签名相同的扩展方法时,只能通过 静态方法方式调用
  3. 当存在2个同名的扩展方法时, 参数更加准确的那个被调用

9. 匿名类

使用new加上初始化器来指定属性。

var dude = new { Name = "Bob", Age = 23 };

编译器会自动编译成一个类型:

internal class AnonymousGeneratedTypeName
{
private string name; // Actual field name is irrelevant
private int age; // Actual field name is irrelevant
public AnonymousGeneratedTypeName (string name, int age)
{
this.name = name; this.age = age;
}
public string Name { get { return name; } }
public int Age { get { return age; } }
// The Equals and GetHashCode methods are overridden (see Chapter 6).
// The ToString method is also overridden.
}
...

1.属性的名称可以根据变量名的名称来推断出来:

var dude = new { Name = "Bob", Age, Age.ToString().Length };
相当于
var dude = new { Name = "Bob", Age = Age, Length = Age.ToString().Length };

2.同一个namespace中2个同属性的匿名类型,他们的类型相同:

var a1 = new { X = 2, Y = 4 };
var a2 = new { X = 2, Y = 4 };
Console.WriteLine (a1.GetType() == a2.GetType()); // True

//并且Equals方法也被重写来判断相等了
Console.WriteLine (a1 == a2); // False
Console.WriteLine (a1.Equals (a2)); // True

猜你喜欢

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