动态编程

博客迁移: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; }
   }
View Code

每当想使用反射编程时候,首先应该考虑能不能改用效率更高的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;
    }
}

 

 

 

 

 

posted @ 2020-02-26 20:02  舒碧  阅读(438)  评论(0编辑  收藏  举报