《Effective C#》快速笔记 - C# 中的动态编程

  静态类型和动态类型各有所长,静态类型能够让编译器帮你找出更多的错误,因为编译器能够在编译时进行大部分的检查工作。C# 是一种静态类型的语言,不过它加入了动态类型的语言特性,可以更高效地解决问题。  

一、目录

  • 三十八、理解动态类型的优劣
  • 三十九、使用动态类型表达泛型类型参数的运行时类型
  • 四十、将接受匿名类型的参数声明为 dynamic
  • 四十一、用 DynamicObject 或 IDynamicMetaObjectProvider 实现数据驱动的动态类型
  • 四十二、如何使用表达式 API
  • 四十三、使用表达式将延迟绑定转换为预先绑定
  • 四十四、尽量减少在公有 API 中使用动态类型

三十八、理解动态类型的优劣

  1. C# 动态类型是为了让静态代码能够更加平滑地与其他使用动态类型的环境进行交互,而不是鼓励在一般场景中使用 dynamic 进行动态编程。
  2. 只要对象在运行时包含成员,那么即可正常使用。
  3. 若是一个操作数(包括 this)为动态类型,那么返回结果也会是动态类型。不过,最后依然要转换成静态类型,以便被其它 C# 代码所使用,以及被编译器感知。
  4. 当你需要不知道具体类型的运行时解析方法的时候,动态类型是最佳的工具。如果你能在编译期间明确类型,那么可以使用 lambda 表达式和函数式编程来解决问题。
  5. 表达式树,一种在运行时创建代码的方法(下面提供示例)。
  6. 大多数情况,可以使用 Lambda 表达式创建泛型 API,让调用者自己动态定义所需要执行的代码即可。
  7. 优先使用静态类型,静态类型比动态类型更高效,动态类型和在运行时创建表达式树都会带来性能上的影响,即便这点影响微不足道。
  8. 若你能控制程序中所有涉及的类型时,可以引入一个接口,而不是动态类型,即基于接口编程,并让所有需要支持该接口行为的类型都实现该接口。通过 C# 类型系统可以减少代码在运行时所产生的错误,编译器也能够生成更加高效的代码。
  9. 动态类型做法的效率比纯粹的静态类型下降挺大,但实现的难度却比解析表达式树要简单地挺多。

  这里,我使用 3 种数字相加的方法,dynamic 动态、Func 委托以及使用表达式树进行相加的区别:

        [TestMethod]
        public void Test()
        {
            var result1 = AddDynamic(2, 3);
            Console.WriteLine((int)result1);

            var result2 = AddFunc(3, 4, (x, y) => x + y);
            Console.WriteLine(result2);

            var result3 = AddExpressionTree(4, 5);
            Console.WriteLine(result3);
        }

        /// <summary>
        /// Add,动态
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private dynamic AddDynamic(dynamic a, dynamic b)
        {
            return a + b;
        }

        /// <summary>
        /// Add,使用委托
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <typeparam name="TR"></typeparam>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        private TR AddFunc<T1, T2, TR>(T1 a, T2 b, Func<T1, T2, TR> func)
        {
            return func(a, b);
        }

        /// <summary>
        /// Add,使用表达式树
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private T AddExpressionTree<T>(T a, T b)
        {
            ParameterExpression leftOperand = Expression.Parameter(typeof(T), "left");
            ParameterExpression rightOperand = Expression.Parameter(typeof(T), "right");
            BinaryExpression body = Expression.Add(leftOperand, rightOperand);
            Expression<Func<T, T, T>> adder = Expression.Lambda<Func<T, T, T>>(body, leftOperand, rightOperand);

            Func<T, T, T> theDelegate = adder.Compile();
            return theDelegate(a, b);
        }
    }

三十九、使用动态类型表达泛型类型参数的运行时类型

  1. System.Linq.Enumerable.Cast<T> 将序列中的对象转换成 T,从而使得 LINQ 可以配合 IEnumerable 进行工作。
  2. Convert<T> 要比 Cast<T> 适用性更广,但同时也会执行更多的工作。

四十、将接受匿名类型的参数声明为 dynamic

  1. 不要过度使用动态类型,因为动态调用会增加系统的额外开销,即便不大。
  2. 长远来看,具体类型更易于维护,编译器和类型系统也会为其提供更好的支持。
  3. 扩展方法不能基于动态对象定义。

四十一、用 DynamicObject 或 IDynamicMetaObjectProvider 实现数据驱动的动态类型

  1. 创建带有动态功能的类型的最简单的方法就是继承 System.Dynamic.DynamicObject。若能直接继承 DynamicObject,那么创建动态类就会比较简单。
  2. 实现 IDynamicMetaObjectProvider 就意味着需要实现方法 GetmetaObject()。
  3. 创建动态类型时首选继承,如果必须使用其他基类,可以手工实现 IDynamicMetaObjectProvider 接口,虽然所有的动态类型都会带来性能上的损失,但这种手工实现接口的方式所带来的损失往往更大一些。

  这是一个实现动态类型模型的一个示例。除了需要继承 DynamicObject,还需要重写 TryGetMemebr() 和 TrySetMemebr()。

        [TestMethod]
        public void Test1()
        {
            dynamic propDynamic = new DynamicPropertyBag();
            propDynamic.Now = DateTime.Now;

            Console.WriteLine(propDynamic.Now);
        }

        /// <summary>
        /// 动态属性绑定模型
        /// </summary>
        internal class DynamicPropertyBag : DynamicObject
        {
            private readonly Dictionary<string, object> _storage = new Dictionary<string, object>();

            /// <summary>
            /// 获取属性值
            /// </summary>
            /// <param name="binder"></param>
            /// <param name="result"></param>
            /// <returns></returns>
            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                var key = binder.Name;

                if (_storage.ContainsKey(key))
                {
                    result = _storage[key];
                    return true;
                }

                result = null;
                return false;
            }

            /// <summary>
            /// 设置属性值
            /// </summary>
            /// <param name="binder"></param>
            /// <param name="value"></param>
            /// <returns></returns>
            public override bool TrySetMember(SetMemberBinder binder, object value)
            {
                var key = binder.Name;

                try
                {
                    if (_storage.ContainsKey(key))
                    {
                        _storage[key] = value;
                    }
                    else
                    {
                        _storage.Add(key, value);
                    }

                    return true;
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    return false;
                }
            }

四十二、如何使用表达式 API

  1. 传统的反射 API 可以用表达式和表达式树进行更好的替代,表达式可以直接编译为委托。
  2. 接口使我们可以得到一个更为清晰、也更具可维护性的系统,反射是一个很强大的晚期绑定机制(虽然效率有所降低),.NET 框架使用它来实现 Windows 控件和 Web 控件的数据绑定。

  这里提供了一个示例:

        [TestMethod]
        public void Test2()
        {
            var result = Call<int, string>((x) => (x * 2).ToString("D"));

            Console.WriteLine(result);
        }

        /// <summary>
        /// 调用
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="op"></param>
        /// <returns></returns>
        private TResult Call<T, TResult>(Expression<Func<T, TResult>> op)
        {
            var exp = op.Body as MethodCallExpression;
            var result = default(TResult);

            if (exp == null)
            {
                return result;
            }

            var methodName = exp.Method.Name;
            var parameters = exp.Arguments.Select(ProcessArgument);

            Console.WriteLine($"方法名 {methodName}");

            foreach (var parameter in parameters)
            {
                Console.WriteLine("参数:");
                Console.WriteLine($"\t{parameter.Item1}:{parameter.Item2}");
            }

            return result;
        }

        /// <summary>
        /// 处理参数
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        private Tuple<Type, object> ProcessArgument(Expression expression)
        {
            object arg = default(object);
            LambdaExpression l = Expression.Lambda(Expression.Convert(expression, expression.Type));

            Type parmType = l.ReturnType;
            arg = l.Compile().DynamicInvoke();

            return Tuple.Create(parmType, arg);
        }

四十三、使用表达式将延迟绑定转换为预先绑定

  1. 延迟绑定API要使用符号(symbol)信息来实现,而预先编译好的 API 则无需这些信息,表达式 API 正是二者之间的桥梁。
  2. 延迟绑定常见于 Silverlight 和 WPF 中使用的属性通知接口,通过实现 INotifyPropertyChanged 和 INotifyPropertyChanging 接口来实现属性变更的预绑定。

四十四、尽量减少在公有 API 中使用动态类型

  1. 优先使用 C# 的静态类型,并尽可能地降低动态类型的作用范围。若是想一直使用动态特性,你应该直接选用一种动态语言,而非 C#。
  2. 若要在程序中使用动态特性,请尽量不要在公有接口中使用,这样会将动态类型限制在一个单独的对象(或类型)中。

本系列

  《Effective C#》快速笔记(一)- C# 语言习惯

  《Effective C#》快速笔记(二)- .NET 资源托管

  《Effective C#》快速笔记(三)- 使用 C# 表达设计

  《Effective C#》快速笔记(四) - 使用框架

  《Effective C#》快速笔记(五) - C# 中的动态编程

  《Effective C#》快速笔记(六) - C# 高效编程要点补充

时间: 2024-10-14 06:10:26

《Effective C#》快速笔记 - C# 中的动态编程的相关文章

[.NET] 《Effective C#》快速笔记 - C# 中的动态编程(初稿)

<Effective C#>快速笔记 - C# 中的动态编程 静态类型和动态类型各有所长,静态类型能够让编译器帮你找出更多的错误,因为编译器能够在编译时进行大部分的检查工作.C# 是一种静态类型的语言,不过它加入了动态类型的语言特性,可以更高效地解决问题. 本系列 <Effective C#>快速笔记(一)- C# 语言习惯 <Effective C#>快速笔记(二)- .NET 资源托管 <Effective C#>快速笔记(三)- 使用 C# 表达设计

[.NET] 《Effective C#》快速笔记 - C# 高效编程要点补充

<Effective C#>快速笔记 - C# 高效编程要点补充 目录 四十五.尽量减少装箱拆箱 四十六.为应用程序创建专门的异常类 四十七.使用强异常安全保证 四十八.尽量使用安全的代码 四十九.实现与 CLS 兼容的程序集 五十.实现小尺寸.高内聚的程序集 这是这一系列的最后一篇. 四十五.尽量减少装箱拆箱 值类型是数据的容器,不支持多态. 装箱把一个值类型放在一个未确定类型的引用对象中,让该值作为引用类型所使用.拆箱指从引用类型的位置取出值的一个副本. 装箱拆箱都是比较影响性能的手段,应

《Effective C#》快速笔记(二)- .NET 资源托管

简介 续 <Effective C#>读书笔记(一)- C# 语言习惯. .NET 中,GC 会帮助我们管理内存,我们并不需要去担心内存泄漏,资源分配和指针初始化等问题.不过,它也并非万能,因为非托管资源需要我们自己进行清理,如文件句柄.数据库连接.GDI+ 对象和COM 对象等. 目录 十二.推荐使用成员初始化器而不是赋值语句 十三.正确地初始化静态成员变量 十四.尽量减少重复的初始化逻辑 十五.使用 using 和 try/finally 清理资源 十六.避免创建非必要的对象 十七.实现标

《Effective C#》快速笔记(四)- 使用框架

.NET 是一个类库,你了解的越多,自己需要编写的代码就越少. 目录 三十.使用重写而不是事件处理函数 三十一.使用 IComparable<T> 和 IComparer<T> 实现顺序关系 三十二.避免使用 ICloneable 接口 三十三.仅用 new 修饰符处理基类更新 三十四.避免重载基类中定义的方法 三十五.PLINQ 如何实现并行算法 三十六.理解 PLINQ 在 I/O 密集场景 三十七.注意并行算法中的异常 三十.使用重写而不是事件处理函数 1.处理系统之中触发的

《Effective C#》快速笔记 - C# 高效编程要点补充

目录 四十五.尽量减少装箱拆箱 四十六.为应用程序创建专门的异常类 四十七.使用强异常安全保证 四十八.尽量使用安全的代码 四十九.实现与 CLS 兼容的程序集 五十.实现小尺寸.高内聚的程序集 这是该系列的最后一篇.也许有些理论有可能会过时,我想它仍有存在的必要,人的知识水平也是一个不断成长的过程,学会站在前人的肩膀上,尝试不断的借鉴与总结. 四十五.尽量减少装箱拆箱 值类型是数据的容器,不支持多态. 装箱把一个值类型放在一个未确定类型的引用对象中,让该值作为引用类型所使用.拆箱指从引用类型的

《Effective C#》快速笔记(一)- C# 语言习惯

目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 is 或 as 操作符而不是强制类型转换 四.使用 Conditional 特性而不是 #if 条件编译 五.为类型提供 ToString() 方法 六.理解几个等同性判断之间的关系 七.理解 GetHashCode() 的陷阱 八.推荐使用查询语法而不是循环 九.避免在 API 中使用转换操作符 十.使用可选参数减少方法重载的数量 十一.理解短小方法的优势 一.使用属性

《Effective C#》快速笔记(三)- 使用 C# 表达设计

<Effective C#>快速笔记(三)- 使用 C# 表达设计 目录 二十一.限制类型的可见性 二十二.通过定义并实现接口替代继续 二十三.理解接口方法和虚方法的区别 二十四.用委托实现回调 二十五.用事件模式实现通知 二十六.避免返回对内部类对象的引用 二十七.让类型支持序列化 二十八.提供组粒度的因特网服务 API 二十九.支持泛型协变和逆变 二十一.限制类型的可见性 1.在保证类型可以完成工作的前提下,应该尽可能地给类型分配最小的可见性. 2.我们经常下意识的创建公有类型.可见性越低

Effective C++_笔记_条款11_在operator=中处理“自我赋值”

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为什么会出现自我赋值呢?不明显的自我赋值,是“别名”带来的结果:所谓“别名”就是“有一个以上的方法指涉对象”.一般而言如果某段代码操作pointers或references而它们被用来“指向多个相同类型的对象”,就需要考虑这些对象是否为同一个.实际上两个对象来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成“别名”.因为一个base class的refe

Effective C++_笔记_条款09_绝不在构造和析构过程中调用virtual函数

(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 为方便采用书上的例子,先提出问题,在说解决方案. 1 问题 1: class Transaction{ 2: public: 3: Transaction(); 4: virtual void LogTransaction() const = 0 ; 5: ... 6: }; 7:  8: Transaction::Transaction() //Base cl