动态编程
博客迁移:https://huangshubi.github.io/2020/02/04/%E5%8A%A8%E6%80%81%E7%BC%96%E7%A8%8B/
记录《Effective C#》C#7.0 学习过程,但是用的是C#版本7.3验证书中例子,书中有些内容是对不上的。配置语言版本
动态编程的优缺点
假如实现一通用加法,参数类型只要求支持特定的操作符,使用泛型是无法实现这种约束的,但动态类型就灵活很多了。
public static dynamic Add(dynamic a, dynamic b) //动态类型 { return a + b; //在运行时才会对类型进行解析。 }
但是运行时才对动态类型解析,因此问题也会延迟暴露。
连锁的动态类型,运算过程中如果有动态类型,那结果也是动态类型,要变成静态类型,只能自己转换类型。(缺 可能C#7.0以后可以自动转了,C#7.3可以自动转)
如果编码无法提前知道对象的类型,并且在运行时要调用某个特定的方法,可以考虑动态编程。其他情应使用lambda表达式或其他函数式编程实现。(建议)
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); }
上面的表达式可以衍生出表达式树的写法
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
的成员。
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 }; }
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:
var results = from MyType v in strList select v; 换成 var results = from v in strList select (MyType); //select方法接受的是lambda表达式,对于v来说,lambda表达式是string类型的对象
解决办法2:strList.Select(a => new MyType { StringMember = a });
解决办法3:使用构造函数。
解决办法4:大量反射代码,知道拿到转换器,但是效率不如动态类型。
最后:动态类型
/// <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,访问属性,会报动态绑定错误。
public static void Main(string[] args) { dynamic dynamicProperties = new DynamicPropertyBag(); try { dynamicProperties.Date = DateTime.Now; } catch(RuntimeBinderException ex) { } }
于是,覆盖原来的TryGetMember、TrySetMember方法。
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方法。
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的方法,把某套逻辑传入该方法,该方法对其进行解析。
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"; } } }
动态的类型转换器,编写可以在运行期自动产生代码的方法。
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返回静态类型。
dynamic a = ""; var b = a + ""; //b是动态类型
动态对象在越小的范围使用越好。
//第一种:不好的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 11 ,2222 ,3333 12 ,2223 ,3334 13 ,2224 ,3335
2: 这里的CSVRow虽然设计为内部私有类,但是TryGetMember是覆盖了父类的。
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; } }