• 动态编程


    博客迁移: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($"	Parameter 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:UsersibiDesktop代码异步ConsoleApp1TextFile1.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;
        }
    }

     

     

     

     

  • 相关阅读:
    LeetCode 282. Expression Add Operators (Hard,递归分治)
    LeetCode 279. Perfect Squares
    LeetCode 278. First Bad Version
    LeetCode 275. H-Index II
    工作笔记——使用Jest时遇到的一些问题
    RFC2616-HTTP1.1-Header Field Definitions(头字段规定部分—译文)
    RFC2616-HTTP1.1-Status Code(状态码规定部分—译文)
    RFC2616-HTTP1.1-Methods(方法规定部分—译文)
    RFC2616-HTTP1.1-Status Code(状态码规定部分—单词注释版)
    RFC2616-HTTP1.1-Methods(方法规定部分—单词注释版)
  • 原文地址:https://www.cnblogs.com/bibi-feiniaoyuan/p/12368729.html
Copyright © 2020-2023  润新知