记录《Effective C#》C#7.0 学习过程,但是用的是C#版本7.3验证书中例子,书中有些内容是对不上的。配置语言版本

动态编程的优缺点

假如实现一通用加法,参数类型只要求支持特定的操作符,使用泛型是无法实现这种约束的,但动态类型就灵活很多了。

1
2
3
4
public static dynamic Add(dynamic a, dynamic b) //动态类型
{
return a + b; //在运行时才会对类型进行解析。
}

但是运行时才对动态类型解析,因此问题也会延迟暴露。

连锁的动态类型,运算过程中如果有动态类型,那结果也是动态类型,要变成静态类型,只能自己转换类型。(缺 可能C#7.0以后可以自动转了,C#7.3可以自动转)

如果编码无法提前知道对象的类型,并且在运行时要调用某个特定的方法,可以考虑动态编程。其他情应使用lambda表达式或其他函数式编程实现。(建议)

1
2
3
4
5
6
7
8
9
10
11
 public static void Main(string[] args)
{
var lambdaAnswer = Add(3, 3, (a, b) => a + b); //传入lambda表达式1
var lambdaAnswer1 = Add("args", 3, (a, b) => a + b.ToString());//传入lambda表达式2
var lambdaAnswer1 = Add(4, 3.4m, (a, b) => a + (int)b);//传入lambda表达式3
}

public static TResult Add<T1,T2,TResult>(T1 left,T2 right,Func<T1,T2,TResult> addMethod)
{
return addMethod(left, right);
}

上面的表达式可以衍生出表达式树的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public static class BinaryOperator<T>  //如果操作数和运算结果同一类型,建议该写法
{
static Func<T,T,T> compiledExpression; //缓存
public static T Add(T left, T right)
{
if (compiledExpression == null)
{
CreatFunc();
}
return compiledExpression(left, right);
}

private static void CreatFunc()
{
var leftOperand = Expression.Parameter(typeof(T), "left");
var rightOperand = Expression.Parameter(typeof(T), "right");
var body = Expression.Add(leftOperand, rightOperand);
var adder = Expression.Lambda<Func<T,T,T>>(body, leftOperand, rightOperand);
compiledExpression = adder.Compile();
}
}

public static class BinaryOperator<T1, T2, TResult>
{
static Func<T1, T2, TResult> compiledExpression;
public static TResult Add(T1 left,T2 right)
{
if (compiledExpression == null)
{
CreatFunc();
}
return compiledExpression(left, right);
}

private static void CreatFunc()
{
var leftOperand = Expression.Parameter(typeof(T1), "left");
var rightOperand = Expression.Parameter(typeof(T2), "right");

Expression convertedLeft = leftOperand;
if (typeof(T1) != typeof(TResult))
{
convertedLeft = Expression.Convert(leftOperand, typeof(TResult)); //转换
}
Expression convertedRight = rightOperand;
if (typeof(T2) != typeof(TResult))
{
convertedRight = Expression.Convert(rightOperand, typeof(TResult));
}

var body = Expression.Add(convertedLeft, convertedRight);
var adder = Expression.Lambda<Func<T1, T2, TResult>>(body, leftOperand, rightOperand);
compiledExpression = adder.Compile();
}
}

C#写出的动态程序(例如表达式树、dynamic…)都是在运行时做检查,效率是没有静态类型的快。

先静后动:通过接口或基类实现,lambda表达式,表达式树,动态类型。

动态编程技术可以帮助运行泛型参数的运行期类型的运用

System.Core程序集里,System.Linq.Enumerable.Cast<T>的扩展方法可以把序列中的每个元素转换成T类型,但是如果对T没有约束,Cast<T>方法只能认定T类型含有的那些System.Object的成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 class Program
{
public static void Main(string[] args)
{
List<string> strList = new List<string>();
strList.Add("aa");
strList.Add("bb");
strList.Add("cc");

var results = strList.Cast<MyType>(); //惰性 延迟转换

//和上面写法一个意思
//var results = from MyType v in strList
//select v;
try
{
foreach(var item in results)
{
Console.WriteLine(item);
}
}
catch(InvalidCastException) //无效转换异常
{
Console.WriteLine("failed");
}
}
}

//结果运行失败

public class MyType
{
public string StringMember { get; set; }

//不推荐设计Api时使用隐式转换器
public static implicit operator String(MyType aString) => aString.StringMember;

public static implicit operator MyType(String aString) => new MyType { StringMember = aString };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void Main(string[] args)
{
List<string> strList = new List<string>();
//是System.Object含有的那些成员,不会报无效转换异常。

strList.Add("aaa");

var results = strList.Cast<string>();

foreach (var item in results)
{
Console.WriteLine(item);
}
}

解决办法1:

1
2
3
4
5
var results = from MyType v in strList  
select v;
换成
var results = from v in strList
select (MyType)v; //select方法接受的是lambda表达式,对于v来说,lambda表达式是string类型的对象

解决办法2:strList.Select(a => new MyType { StringMember = a });

解决办法3:使用构造函数。

解决办法4:大量反射代码,知道拿到转换器,但是效率不如动态类型。

最后:动态类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <summary>
/// 枚举扩展类
/// </summary>
public static class EnumerableExtension
{
public static IEnumerable<TResult> Convert<TResult>(this System.Collections.IEnumerable sequence)
{
foreach(object item in sequence)
{
dynamic result = (dynamic)item;
yield return (TResult)result; //yield 迭代返回
}
}
}

使用DynamicObject实现数据驱动的动态类型

直接继承DynamicObject,访问属性,会报动态绑定错误。

1
2
3
4
5
6
7
8
9
10
11
12
public static void Main(string[] args)
{
dynamic dynamicProperties = new DynamicPropertyBag();
try
{
dynamicProperties.Date = DateTime.Now;
}
catch(RuntimeBinderException ex)
{

}
}

于是,覆盖原来的TryGetMember、TrySetMember方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class DynamicPropertyBag : DynamicObject
{
private Dictionary<string, object> storage = new Dictionary<string, object>();

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (storage.ContainsKey(binder.Name))
{
result = storage[binder.Name];
return true;
}
result = null;
return false;
}

public override bool TrySetMember(SetMemberBinder binder, object value)
{
string key = binder.Name;
if (storage.ContainsKey(key))
{
storage[key] = value;
}
else
{
storage.Add(key, value);
}
return true;
}
}

LINQ TO XML不是特别好用。如果想实现A元素.B元素[“C”,3]这样的链式,和两个同级索引获取值方法,可以覆盖DynamicObject的TryGetMember方法、TryGetIndex方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class DynamicXElement : DynamicObject
{
private readonly XElement xmlSource;

public DynamicXElement(XElement source)
{
xmlSource = source;
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (xmlSource == null)
{
if (binder.Name == "Value")
{
result = "";
}
else
{
result = null;
return false;
}
}
else
{
if (binder.Name == "Value")
result = xmlSource.Value;
else
result = new DynamicXElement(xmlSource.Element(XName.Get(binder.Name)));
}
return true;
}

public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
result = null;
if (indexes.Length != 2)
return false;
if (!(indexes[0] is string))
return false;
if (!(indexes[1] is int))
return false;
if (xmlSource == null)
return false;

var allnodes = xmlSource.Elements(indexes[0].ToString());
var index = (int)indexes[1];

if (index < allnodes.Count())
{
result = new DynamicXElement(allnodes.ElementAt(index));
return true;
}
else
{
result = null;
return false;
}
}
}
Expression API

WCF、Web服务等常针对某项服务生成对于的代理,如果服务器那边更新了方法,客户端代理需要相应的更新。但是使用Express API,就相对简单。

创建能够接受Expression的方法,把某套逻辑传入该方法,该方法对其进行解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
namespace ConsoleApp1
{
//一个简单的例子:服务方法的参数是常量,打印方法名,参数类型,参数值。
class Program
{
public static void Main(string[] args)
{
var client = new ClientProxy<IService>();
client.CallInterface<string>(server=>server.DoWork(666));
}
}

public class ClientProxy<T>
{
public TResult CallInterface<TResult>(Expression<Func<T,TResult>> op)
{
var exp = op.Body as MethodCallExpression; //静态方法或实例方法的调用
var methodName = exp.Method.Name;
var methodInfo = exp.Method;
var allParameters = from element in exp.Arguments
select ProcessArgument(element);

Console.WriteLine($"Calling {methodName}");

foreach(var param in allParameters)
{
Console.WriteLine($"\tParameter type={param.ParamType} value={param.ParamValue}");
}

//如何动态的传入参数 ??????????????????????
//????????????????????????????
//var result = op.Compile();// (()allParameters.First().ParamValue);

return default(TResult);
}

//处理的参数是个常量
private (Type ParamType,object ParamValue) ProcessArgument(Expression element)
{
//通过先构造一个委托类型来创建一个 System.Linq.Expressions.LambdaExpression。
LambdaExpression expression = Expression.Lambda(Expression.Convert(element, element.Type));
//获取lambda返回的类型
Type paramType = expression.ReturnType;
//动态调用当前委托表示的方法
var argument = expression.Compile().DynamicInvoke();
return (paramType, argument);
}
}

public interface IService
{
string DoWork(int number);
}

public class ImplementService : IService
{
public string DoWork(int number)
{
return "hello world";
}
}
}

动态的类型转换器,编写可以在运行期自动产生代码的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class Program
{
public static void Main(string[] args)
{
var converter = new Converter<Source,Dest>();
Source source = new Source();
source.AA = "AA";
source.BB = 22;
source.CC = 66;
source.EE = 88;
var dest = converter.ConvertFrom(source);
}
}

public class Converter<TSource, TDest>
{
Func<TSource, TDest> converter;
public TDest ConvertFrom(TSource source)
{
if (converter == null)
{
CreateConverter();
}
return converter(source);
}

private void CreateConverter()
{
//创建一个ParameterExpression节点,标识表达式树中的参数或变量
var source = Expression.Parameter(typeof(TSource), "source");
//创建一个ParameterExpression节点,标识表达式树中的参数或变量
var dest = Expression.Variable(typeof(TDest), "dest");

var assignments = from srcProp in
typeof(TSource).
GetProperties(BindingFlags.Public | BindingFlags.Instance)
where srcProp.CanRead
let destProp = typeof(TDest).GetProperty(srcProp.Name, BindingFlags.Public | BindingFlags.Instance)
where (destProp != null) && (destProp.CanWrite)
select Expression.Assign(Expression.Property(dest, destProp), Expression.Property(source, srcProp));

var body = new List<Expression>();
body.Add(Expression.Assign(dest,Expression.New(typeof(TDest))));
body.AddRange(assignments);
body.Add(dest);

var expr = Expression.Lambda<Func<TSource, TDest>>(Expression.Block(new[] { dest},body.ToArray()),source);

var func = expr.Compile();
converter = func;
}
}

public class Source
{
public string AA { get; set; }
public int BB { get; set; }
public double CC { get; set; }
private double DD { get; set; } //无法赋值
public double EE { get; set; } //无法赋值
}

public class Dest
{
public string AA { get; set; }
public int BB { get; set; }
public double CC { get; set; }
private double DD { get; set; }
public static double EE { get; set; }
}

每当想使用反射编程时候,首先应该考虑能不能改用效率更高的Expression API。

减少公有API中的动态对象

动态对象具有传染性,建议API返回静态类型。

1
2
dynamic a = "";
var b = a + ""; //b是动态类型

动态对象在越小的范围使用越好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//第一种:不好的API,需要传入动态类型参数。
public static dynamic Add(dynamic a, dynamic b) //动态类型
{
return a + b; //在运行时才会对类型进行解析。
}
//改良 参数改为泛型,返回类型转为静态
public static TResult Add<T1,T2,TResult>(T1 t1,T2 t2)
{
return (TResult)Add(t1, t2);
dynamic Add(dynamic t11,dynamic t12)
{
return t11 + t12;
}
}

有时候,确实需要把动态对象放到接口中,但不代表整个接口都是动态代码,只应该把要依赖动态对象才能运作的成员设计成动态的。

举例:https://github.com/JoshClose/CsvHelper ,CSV数据的读取器。

1: 建立一个文本文件,内容如下

1
2
3
4
列1 ,列2 , 列3
11 ,2222 ,3333
12 ,2223 ,3334
13 ,2224 ,3335

2: 这里的CSVRow虽然设计为内部私有类,但是TryGetMember是覆盖了父类的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
namespace ConsoleApp1
{
class Program
{
public static void Main(string[] args)
{
var data = new CSVDataContainer(new System.IO.StreamReader(@"C:\Users\bibi\Desktop\代码\异步\ConsoleApp1\TextFile1.txt"));
foreach(var item in data.Rows)
{
Console.WriteLine($"{item.列1} {item.列2} {item.列3}");
}

dynamic a = "";
var b = a + "";
}

public static TResult Add<T1,T2,TResult>(T1 t1,T2 t2)
{
return (TResult)Add(t1, t2);
dynamic Add(dynamic t11,dynamic t12)
{
return t11 + t12;
}
}
}

public class CSVDataContainer
{
private class CSVRow : DynamicObject
{
private List<(string, string)> values = new List<(string, string)>();
public CSVRow(IEnumerable<string> headers,IEnumerable<string> items)
{
values.AddRange(headers.Zip(items, (header, value) => (header, value)));
}

//虽然CSVRow是私有,但这个依然可以覆盖。
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var answer = values.FirstOrDefault(n => n.Item1 == binder.Name);
result = answer.Item2;
return result != null;
}
}

private List<string> columnNames = new List<string>();
private List<CSVRow> data = new List<CSVRow>();

public CSVDataContainer(System.IO.TextReader stream)
{
var headers = stream.ReadLine();
columnNames = (from header in headers.Split(',') select header.Trim()).ToList();
var line = stream.ReadLine();
while(line != null)
{
var items = line.Split(',');
data.Add(new CSVRow(columnNames, items));
line = stream.ReadLine();
}
}

public dynamic this[int index]=>data[index];
public IEnumerable<dynamic> Rows => data;
}
}