c# 创建委托 消息订阅_.NET / C# 面试题(五)—.NET 中的高级特性

委托

请解释委托的基本原理

委托是一类继承自System.Delegate的类型,每个委托对象至少包含了一个指向某个方法的指 针,该方法可以是实例方法,也可以是静态方法。委托实现了回调方法的机制,能够帮助程序员设计更加简洁优美的面向对象程序。

委托回调静态方法和实例方法有何区别

当委托绑定静态方法时,内部的对象成员变量_target将会被设置成null,而当委托绑定实例 方法时,_target将会设置成指向该实例方法所属类型的一个实例对象,当委托被执行时,该对象 实例将被用来调用实例方法。

什么是链式委托

链式委托是指一个由委托串成的链表,当链表上的一个委托被回调时,所有链表上该委托的后续委托将会被顺序执行。

链式委托的执行顺序是怎么样的

链式委托的执行顺序是:按照委托链上的顺序从当前委托开始依次往后执行。如果有需要,可以通过GetlnvocationListO方法来获得委托链上所有需要执行的委托,并且按照任何希望的顺序 去执行它们。

可否定义拥有返回值的方法的委托链

委托可以是带有返回值的方法,但多于一个带返回值的方法被添加到委托链中时,程序员需要手动地调用委托链上的每个方法,否则委托使用者将只能得到委托链上最后一个被执行方法的返回值。

委托通常可以应用在哪些场合

委托的应用场合通常是任务的执行者把细节工作进行再分配,执行者确切地知道什么工作将要被执行,但却把执行细节委托给其他组件、方法或者程序集。

事件

请解释事件的基本使用方法

事件是一种使对象或类能够提供通知的成员。客户端可以通过提供事件处理程序为相应的事件添加可执行代码.事件是一种特殊的委托。

事件和委托有何联系

事件是一个委托类型,该委托类型的方法无返回值,并且拥有两个参数:object 与 TEventArg。 事件的订阅和取消都是基于委托链表来实现的。

如何设计一个带有很多事件的类型

当某个类型包含较多的事件时,可以考虑集中把所有事件的委托链表存储在-个集合之中,这样做能有效地减少对象的大小,因为在实际逻辑世界中一个对象使用所有事件的概率相对很低。.NET的内建类型 System.ComponentModel.EventHandlerList 提供了这样一个存储事件集合的封装。

用代码表示如下情景:猫叫、老鼠逃跑、主人惊醒

这是一个典型的观察者模式的应用场景,事件的发源在于猫叫这个动作,在猫叫之后,老鼠开始逃跑,而主人则会从睡梦中惊醒。可以发现,主人和老鼠这两个类型的动作相互之间没有联系,但都是由猫叫这一事件触发的。

  设计的大致思路在于,猫类包含并维护一个猫叫的动作,主人和老鼠的对象实例需要订阅猫叫这一事件,保证猫叫这一事件发生时主人和老鼠可以执行相应的动作。

//(1)设计猫类,为其定义一个猫叫的事件 CatCryEvent:using System;namespace ConsoleApp1{    ///     /// 猫类    ///     public class Cat    {        private string name;        // 猫叫的事件        public event EventHandler CatCryEvent;        public Cat(string name)        {            this.name = name;        }        // 触发猫叫事件        public void CatCry()        {            // 初始化事件参数            CatCryEventArgs args = new CatCryEventArgs(name);            Console.WriteLine(args);            // 开始触发事件            CatCryEvent(this, args);        }    }}using System;namespace ConsoleApp1{    ///     /// 猫的事件类    ///     public class CatCryEventArgs    {        private string catName;        public CatCryEventArgs(string catName)            : base()        {            this.catName = catName;        }        public override string ToString()        {            string message = string.Format("{0}叫了", catName);            return message;        }    }}
// (2)设计老鼠类,在其构造方法中订阅猫叫事件,并提供对应的处理方法using System;namespace ConsoleApp1{    ///     /// 老鼠类    ///     public class Mouse    {        private string name;        // 在构造方法中订阅事件        public Mouse(string name, Cat cat)        {            this.name = name;            cat.CatCryEvent += CatCryEventHandler;        }        // 猫叫的处理方法        private void CatCryEventHandler(object sender, CatCryEventArgs e)        {            Run();        }        // 逃跑方法        private void Run()        {            Console.WriteLine("{0}逃走了:我勒个去,赶紧跑啊!", name);        }    }}
using System;namespace ConsoleApp1{    ///     /// 主人类    ///     public class Master    {        private string name;        // 在构造方法中订阅事件        public Master(string name, Cat cat)        {            this.name = name;            cat.CatCryEvent += CatCryEventHandler;        }        // 针对猫叫的处理方法        private void CatCryEventHandler(object sender, CatCryEventArgs e)        {            WakeUp();        }        // 具体的处理方法——惊醒        private void WakeUp()        {            Console.WriteLine("{0}醒了:我勒个去,叫个锤子!", name);        }    }}
//(4)最后在Main方法中进行场景的模拟:using System;namespace ConsoleApp1{    ///     /// 场景模拟    ///     class Program    {        static void Main(string[] args)        {            Console.WriteLine("开始模拟");            Console.WriteLine("===================");            Cat cat = new Cat("汤姆猫");            Mouse mouse1 = new Mouse("米老鼠", cat);            Mouse mouse2 = new Mouse("杰瑞鼠", cat);            Master master = new Master("小明", cat);            // 毛开始叫了,老鼠和主人有不同的反应            cat.CatCry();            Console.ReadKey();        }    }}

这里定义了一只猫,两只老鼠与一个主人,当猫的CatCry方法被执行到时,会触发猫叫事件 CatCryEvent,此时就会通知所有这一事件的订阅者。本场景的关键之处就在于主人和老鼠的动作应该完全由猫叫来触发。下面是场景模拟代码的运行结果:

f1e423da482e0fd77d79f94bb4bdc0d8.png

反射

请解释反射的基本原理和其实现的基石

反射是一种动态分析程序集、模块、类型、字段等目标对象的机制,它的实现依托于元数据。元数据是存储在PE文件中的数据块,它详细记录了程序集或模块内部的结构、引用类型、程序集和清单。

.NET 提供了哪些类型来实现反射

2e051efc1409413be5b5906b5e0928a6.png

在 System.Reflection命名空间下, .NET提供了丰富的实现反射机制的类型,可以达到读取元数据中所有信息并且动态创建类型对象的功能。详细的类型列表和使用方法请参考本节的分析问题。

如何实现动态发射(生成)程序集

8b4945c5a20d6aa3e4c7a1532abbe567.png
using System;using System.Collections.Generic;using System.Text;using System.Reflection.Emit;using System.Threading;using System.Reflection;namespace NET.MST.Sixth.EmitAssembly{    class MainClass    {        ///         /// 使用发射的类型        ///         ///         static void Main(string[] args)        {            //定义构造方法的参数            Object[] ctorParams = new Object[2];            ctorParams[0] = 1000;            ctorParams[1] = 2000;            //发射程序集,并得到AddClass类型            Type type = CreateAssembly();            //新建AddClass对象            object ptInstance = Activator.CreateInstance(type, ctorParams);            //调用动态发射的ToString方法            Console.WriteLine(ptInstance.ToString());            //调用动态发射的GetResult方法            MethodInfo info = type.GetMethod("GetResult",new Type[0]);            long result = (long)info.Invoke(ptInstance, null);            Console.WriteLine(result.ToString());            Console.Read();        }        ///         /// 用来动态发射一个程序集的中间代码        /// 放回发射的类型        /// 创建的中间代码相当于这样的C#代码:        // public class AddClass        //{        //     private long first;        //     private long second;                //     public AddClass(long f, long s)        //     {        //         first = f;        //         second = s;        //     }        //     public long GetResult()        //     {        //         return first + second;        //     }                //     public override string ToString()        //     {        //         return      "第一个数字是:" +        //                     first.ToString() +        //                        "第二个数字是:" +        //                       second.ToString();        //     }        // }        ///         static Type CreateAssembly()        {            //在当前应用程序域中定义新程序集            AppDomain myDomain = Thread.GetDomain();            AssemblyName myAsmName = new AssemblyName();            myAsmName.Name = "NewAssembly";            AssemblyBuilder assemblybuilder = myDomain.DefineDynamicAssembly(myAsmName, AssemblyBuilderAccess.RunAndSave);            //定义模块            ModuleBuilder addclassModule = assemblybuilder.DefineDynamicModule("AddClass", "AddClass.dll");            //定义模块中的类型            TypeBuilder addclass = addclassModule.DefineType("AddClass", TypeAttributes.Public);            //这个类型将包含两个私有成员            //名字分别为:first和second            FieldBuilder first = addclass.DefineField("first", typeof(long), FieldAttributes.Private);            FieldBuilder second = addclass.DefineField("second", typeof(long), FieldAttributes.Private);            //为AddClass定义一个公共构造方法            //接受两个长整型参数            Type[] ctorParams = new Type[] { typeof(long), typeof(long) };            //AddClass的基类是System.Object            Type objType = Type.GetType("System.Object");            //得到无参数构造方法            ConstructorInfo objCtor = objType.GetConstructor(new Type[0]);            //AddClass的公共构造方法            ConstructorBuilder addCtor = addclass.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ctorParams);            //开始生成构造方法的中间代码            ILGenerator ctorIL = addCtor.GetILGenerator();            ctorIL.Emit(OpCodes.Ldarg_0);            ctorIL.Emit(OpCodes.Call, objCtor);            ctorIL.Emit(OpCodes.Ldarg_0);            ctorIL.Emit(OpCodes.Ldarg_1);            ctorIL.Emit(OpCodes.Stfld, first);            ctorIL.Emit(OpCodes.Ldarg_0);            ctorIL.Emit(OpCodes.Ldarg_2);            ctorIL.Emit(OpCodes.Stfld, second);            ctorIL.Emit(OpCodes.Ret);            //这里生成long GetResult()方法            //用以得到两个数字相加的结果            MethodBuilder resultMethod = addclass.DefineMethod("GetResult", MethodAttributes.Public, typeof(long), new Type[0]);            //发射GetResult方法的中间代码            ILGenerator resultIL = resultMethod.GetILGenerator();            // ILGenerator.EmitWriteLine(string) 生成一个字符串对象,            //并且通过控制台输出             resultIL.EmitWriteLine("开始执行相加:");            //执行相加程序            //这里的IL代码用来导入两个成员变量,相加并返回结果            resultIL.Emit(OpCodes.Ldarg_0);            resultIL.Emit(OpCodes.Ldfld, first);            resultIL.Emit(OpCodes.Ldarg_0);            resultIL.Emit(OpCodes.Ldfld, second);            resultIL.Emit(OpCodes.Add);            resultIL.Emit(OpCodes.Ret);            //发射String ToString方法            MethodBuilder tostringMethod = addclass.DefineMethod("ToString", MethodAttributes.Virtual | MethodAttributes.Public, typeof(String), new Type[0]);            ILGenerator stringIL = tostringMethod.GetILGenerator();            stringIL.Emit(OpCodes.Ldstr,"第一个数字是:");            stringIL.Emit(OpCodes.Ldarg_0);            stringIL.Emit(OpCodes.Ldflda, first);            stringIL.Emit(OpCodes.Call, typeof(long).GetMethod("ToString",new Type[0]));            stringIL.Emit(OpCodes.Ldstr, "第二个数字是:");            stringIL.Emit(OpCodes.Ldarg_0);            stringIL.Emit(OpCodes.Ldflda, second);            stringIL.Emit(OpCodes.Call, typeof(long).GetMethod("ToString", new Type[0]));            Type[] types = new Type[4];            for (int i = 0; i < types.Length; i++)                types[i] = typeof(String);            stringIL.Emit(OpCodes.Call, typeof(String).GetMethod("Concat",types));            stringIL.Emit(OpCodes.Ret);            //说明方法重载System.Object方法            addclass.DefineMethodOverride(tostringMethod, typeof(System.Object).GetMethod("ToString"));            return addclass.CreateType();        }    }}

利用反射实现工厂模式

相关实例代码:https://blog.csdn.net/weixin_34174422/article/details/85835188

使用反射可以实现灵活性较高的工厂模式,其关键在于动态地查找产品所包含的所有零件,而不需要通过代码来逐 分析使用者的需求。反射工厂模式具有灵活性高、运行效率相对较低的特点。

如何以较小的内存代价保存Type、Field 和 Method 信息

System.RundmeTypeHandles System.RuntimeMethodHandle 和 System.RuntimeFieldHandle 三个类型分别包含了一个指向类型、方法和字段描述的指针,用保存指针的方式来代替保存整个类型、方法和字段的信息描述对象,可以有效地减少内存的消耗。而在实际需要用到这些信息时,又可以通过这三个句柄类型对象,分 别 得 到 System.Type、System.Reflection.Methodlnfo和 System.Reflection.Fieldlnfo 类型对象。

特性

什么是特性,如何自定义一个特性

特性是一种特殊的用以申明式编程的机制,特性类型是一族继承自System-Attribute的类型, 在编译时,特性的使用会被写入元数据中,以供运行或者程序中的反射使用。自定义一个特性,本质就是定义一个继承自System.Attribute的类型。

.NET 中特性可以在哪些元素上使用

特性可以应用在程序集、模块、类、结构、枚举、构造方法、方法、属性、字段、事件、接口、参数、委托、返回参数和泛型参数这些目标元素之上。通过AttributeUsage特性可以限制自定 义特性的使用范围。

有哪几种方法可以获知一个元素是否申明某个特性

.NET提供了多种方法来检查某个元素上的特性,常用的方法包括:System.Attribute.IsDefined 方 法 、System.Attribute.GetCustomAttribute 方 法 、Systcm.Attribute. GetCustomAttributes 方法和 System.Reflection.CustomAttributeData类型。读者在使用这些方法时,需要留意是否需要实例化特性,因为这意味着元数据中的字节流将被执行,这可能成为一个安全隐患。

一个元素是否可以重复申明同一个特性

当一个特性申明了 AttributeUsage特性并且显式地将AllowMultiple属性设置为true时,该特性就可以在同一元素上多次申明,否则的话编译器将报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值