Skip to main content
 Web开发网 » 站长学院 » 浏览器插件

.NET / C# 面试题(五)—.NET 中的高级特性

2021年10月11日6050百度已收录

委托

请解释委托的基本原理

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

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

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

什么是链式委托

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

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

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

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

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

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

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

事件

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

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

事件和委托有何联系

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

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

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

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

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

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

//(1)设计猫类,为其定义一个猫叫的事件 CatCryEvent:using System;namespace ConsoleApp1{ /// <summary> /// 猫类 /// </summary> public class Cat { private string name; // 猫叫的事件 public event EventHandler<CatCryEventArgs> 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{ /// <summary> /// 猫的事件类 /// </summary> 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{ /// <summary> /// 老鼠类 /// </summary> 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{ /// <summary> /// 主人类 /// </summary> 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{ /// <summary> /// 场景模拟 /// </summary> 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,此时就会通知所有这一事件的订阅者。本场景的关键之处就在于主人和老鼠的动作应该完全由猫叫来触发。下面是场景模拟代码的运行结果:

.NET / C# 面试题(五)—.NET 中的高级特性  .net面试经验 第1张

反射

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

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

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

.NET / C# 面试题(五)—.NET 中的高级特性  .net面试经验 第2张

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

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

.NET / C# 面试题(五)—.NET 中的高级特性  .net面试经验 第3张

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 { /// <summary> /// 使用发射的类型 /// </summary> /// <param name="args"></param> 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(); } /// <summary> /// 用来动态发射一个程序集的中间代码 /// 放回发射的类型 /// 创建的中间代码相当于这样的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() + // "\r\n第二个数字是:" + // second.ToString(); // } // } /// </summary> 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, "\r\n第二个数字是:"); 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(); } }}

利用反射实现工厂模式

相关实例代码:

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

如何以较小的内存代价保存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时,该特性就可以在同一元素上多次申明,否则的话编译器将报错。

评论列表暂无评论
发表评论
微信