.net core精彩实例分享 -- 反射与Composition
文章目录
- 介绍
- 具体案例
- 用Activator类创建类型实例
- 检查类型上所应用的自定义Attribute
- 通过协定来约束导出类型
- 导入多个类型
- 封装元数据
- 总结
介绍
随着.net core
越来越流行,对.net core
基础知识的了解,实际应用等相关的知识也应该有所了解。所以就有了这篇文章,案例都是来自阅读的书籍,或者实际工作中感觉比较有用的应用。分享亦总结。
本文主要介绍 .net core
相关的反射与Composition案例。
具体案例
用Activator类创建类型实例
【导语】
除了通过 ConstructorInfo
来创建类型实例外,还可以使用 Activator
类。这是一个静态类,因此它的所有成员方法都是静态方法,该类仅包含一个方法————CreateInstance
,用于创建类型实例,但该方法由多个重载,最为常用的有以下两个重载:
(1)当要使用类型中带有无参数的构造函数时,应该用以下重载:
static object CreateInstance(Type type);
(2)当要使用类型中带参数的构造函数时,应该用以下重载:
static object CreateInstance(Type type, params object[] args);
args
是传递给构造函数的参数值列表,依照构造函数声明的参数顺序传入即可。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:声明 Person
类,该类的构造函数需要三个参数。
public class Person
{public Person(string name, string city, int age){Name = name;City = city;Age = age;}public string Name { get; }public string City { get; }public int Age { get; }
}
步骤3:获取 Person
类关联的 Type
对象。
Type theType = typeof(Person);
步骤4:创建 Person
实例。
object instance = Activator.CreateInstance(theType, "Mee Yang", "Zhong Shan", 21);
由于 Person
类的构造函数需要三个参数,因此在调用 CreateInstance
方法时要传递相应的参数值。
步骤5:现在,Person
类的实例已经创建。下面代码将通过反射枚举出 Person
对象的公共属性,然后输出各个属性的值。
PropertyInfo[] props = theType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach(PropertyInfo p in props)
{Console.WriteLine($"{p.Name} : {p.GetValue(instance)}");
}
要一次性获取多个属性的信息,应当调用 Type
对象的 GetProperties
方法。
步骤6:运行应用程序项目,结果如下。
检查类型上所应用的自定义Attribute
【导语】
CustomAttributeExtensions
类提供了一系列扩展方法,可以获取程序集、类型、类型成员、参数上应用的自定义特性(Attribute
)实例。
如果实现知道特性的类型,则可以使用带泛型参数的方法:
T GetCustomAttribute<T>(this ...) where T : Attribute;
此方法调用起来是最简单的,可以直接返回目标特性的实例。但是如果使用以下方法来获取自定义的特性实例,则可能需要进行类型转换,因为它的返回类型为 Attribute
(特性类的公共基类)。
Attribute GetCustomAttribute(this ..., Type attributeType);
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:声明一个特性类,仅应用于类上面。
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class AliasNameAttribute : Attribute
{public AliasNameAttribute(string aliasName){Alias = aliasName;}public string Alias { get; } = null;
}
特性类必须是
Attribute
的派生类,或者间接派生类。
步骤3:声明一个测试类,并在类上应用上述特性。
[AliasName("order_data")]
public class CoreData
{}
步骤4:获取 CoreData
上所应用的 AliasNameAttribute
。
// 获取与类型相关的 Type 对象
Type testType = typeof(CoreData);
// 获取特性类实例
AliasNameAttribute attr = testType.GetCustomAttribute<AliasNameAttribute>();
// 输出特性类的属性值
if(attr != null)
{Console.WriteLine($"类型 {testType.Name} 的别名是:{attr.Alias}");
}
步骤5:运行应用程序项目,结果如下。
通过协定来约束导出类型
【导语】
Composition
技术主要用于程序扩展,它会根据协定标识主动发现已导出的类型,并把该类型导入和组合到特定实例上,这样应用程序代码就能使用这些导入的类型了。
默认情况下,.NET Core
框架不包含 Composition
相关的 API
,开发人员需要通过 NuGet
手动安 System.Composition
装包。
在要作为扩展组件的类上应用 ExportAttribute
后,该类便被标识为可导出类型,即它可以被 Composition
引擎发现。ExportAttribute
类有两个很重要的属性:
(1)ContractName
属性:类型协定的名称。开发人员可以自定义该名称,必须要保证该名称在所有导出类型中的唯一性,否则协定名称就失去实际用途了(就是用来标识类型协定的)。
(2)ContractType
属性:协定的类型。如果不指定该属性,默认的类型是跟随在 ExportAttribute
之后的类型(即该特性所应用的目标类)。
为了让扩展的组件具有规律性(存在相似特性),以便于在运行时灵活使用,通常会为所有待导出的类定义一个共同的接口,然后这些类都实现这个接口。这样对于类型的导入者而言,只需要认准这个通用的接口,而不必考虑具体的实现类,可以轻松导入并调用多个类型。
本实例将演示通过指定唯一命名与类型协定来标识导出的类型,这样做既能保证导出的类型具有唯一的标识,又可以保持兼容性。本例中所有导出的类都会实现 IPlayer
接口。尽管 被导出的类型都被约束为 IPlayer
,但是每个导出的类型都设置了协定名称(确保不会重复出现)。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:安装 System.Composition
NuGet
包。
步骤3:引入命名空间。
using System;
using System.Composition;
using System.Composition.Hosting;
using System.Reflection;
步骤4:声明 IPlayer
接口,作为导出类型的公共规范。
public interface IPlayer
{void PlayTracks();
}
步骤5:定义两个实现 IPlayer
接口的类,并应用 ExportAttribute
。
[Export("gen_pl", typeof(IPlayer))]
public class GenPlayer : IPlayer
{public void PlayTracks(){Console.WriteLine("在普通播放器上播放音乐");}
}
[Export("pro_pl", typeof(IPlayer))]
public class ProPlayer : IPlayer
{public void PlayTracks(){Console.WriteLine("在专业播放器上播放音乐");}
}
步骤6:回到 Main
方法中,获取当前正在执行的程序集。
Assembly currAss = Assembly.GetExecutingAssembly();
步骤7:创建 ContainerConfiguration
实例,设置导出类型的查找范围位于当前程序中。
ContainerConfiguration config = new ContainerConfiguration().WithAssembly(currAss);
步骤8:创建 CompositionHost
容器,并获取导出类型的实例。
using(CompositionHost host = config.CreateContainer())
{IPlayer p1 = host.GetExport<IPlayer>("gen_pl");IPlayer p2 = host.GetExport<IPlayer>("pro_pl");
}
在调用 GetExport
方法时,需要传递每个导出类型所对应的协定名称。
步骤9:分别调用两个对象实例的 PlayTracks
方法。
using(CompositionHost host = config.CreateContainer())
{...p1.PlayTracks();p2.PlayTracks();
}
步骤10:运行应用程序项目,结果如下。
导入多个类型
【导语】
Composition
技术支持将类型导入某个类的属性(或方法参数)中。应用了 ImportAttribute
的属性只可以导入单个类型实例,而应用了 ImportManyAttribute
的属性则可以导入多个类型实例,此时属性一般声明为 IEnumerator<T>
类型,Composition
容器在导入类型时会自动填充该属性。
本实例中,以
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:定义 IAnimal
接口,公开两个属性。
public interface IAnimal
{string Name { get; }string Family { get; }
}
步骤3:定义三个实现 IAnimal
接口的类,并标识为导出类型。
[Export(typeof(IAnimal))]
public class FelisCatus : IAnimal
{public string Name => "家猫";public string Family => "猫科";
}
[Export(typeof(IAnimal))]
public class SolenopsisInvictaBuren : IAnimal
{public string Name => "红火蚁";public string Family => "蚁科";
}
[Export(typeof(IAnimal))]
public class HeliconiusMelpomene : IAnimal
{public string Name => "红带袖蝶";public string Family => "凤蝶科";
}
步骤4:定义 SomeAnimalSamples
类,并把 AnimalList
标记为可导入多个类型。
class SomeAnimalSamples
{[ImportMany]public IEnumerable<IAnimal> AnimalList { get; set; }
}
记得要在属性上应用
ImportManyAttribute
,因为Composition
在组合类型时会查找该特性。
步骤5:回到 Main
方法,将当前程序集作为 Composition
搜索导出类型的范围。
Assembly currentAssembly = Assembly.GetExecutingAssembly();
ContainerConfiguration config = new ContainerConfiguration().WithAssembly(currentAssembly);
步骤6:创建 Composition
容器,并将导入的类型组合到 SomeAnimalSamples
对象的 AnimalList
属性中。
SomeAnimalSamples samples = new SomeAnimalSamples();
using(CompositionHost container = config.CreateContainer())
{container.SatisfyImports(samples);
}
步骤7:测试访问导入的类型实例。
foreach (IAnimal an in samples.AnimalList)
{Console.WriteLine($"Name: {an.Name}\nFamily: {an.Family}\n");
}
步骤8:运行应用程序项目,结果如下。
封装元数据
【导语】
在导出类型的时候,可以同时将数据导出。元数据可以理解为一系列附加信息,这些数据与类型相关但不参与执行,仅仅对类型做出额外的描述。在要导出的类型上应用 ExportMetadataAttribute
可以添加要导出的元数据,它包含两个值:Name
是元数据字段的名称,类字为字符串;Value
是与字段对应的值,类型为 object
,即每条元数据的结构类似于字典。要指定多条元数据,可以在导出的类型上引用多个 ExportMetadataAttribute
。
在导入时,可以使用 Lazy<T,TMetadata>
类型的对象来接受导入的类型与元数据。Lazy
类提供了一种机制————类型可以延迟初始化,即当 Value
属性被访问时才会调用T类型的构造器。TMetadata
表示导入的元数据,一般情况下,可以使用 IDictionary<string, object>
类型来接收元数据,也可以使用一个自定义的类来接收(带无参数构造函数的类)。
导入的元数据,不仅仅可以使用 IDictionary<string, object>
类型来接收,还可以使用自定义的类来接收,此自定义类需要满足两个条件:
(1)包含公共的无参数构造函数。因为在填充元数据时,类实例由 Composition
自动创建,而不是从代码种显示调用构造函数。
(2)该类中的属性名称必须与导出的元数据的 Name
属性匹配,而且是区分大小写的。
若元数据的条目比较多,使用多个 ExportMetadataAttribute
对象来指定元数据会显得比较麻烦。这时候可以用一个类来封装元数据,但要注意以下两点:
(1)封装元数据的类需要应用 ExportMetadataAttribute
进行标注。
(2)这个封装类应当从 Attribute
类派生。因为导出类型的元数据是附加信息,以特性的形式应用到导出类型的定义上。
【操作流程】
步骤1:定义协定接口。
public interface ITest
{void RunTask();
}
步骤2:定义元数据的封装类。
[MetadataAttribute]
class CustMetadataAttribute : Attribute
{public string Author { get; set; }public string Description { get; set; }public int Version { get; set; }public CustMetadataAttribute(string author, string desc, int ver){Author = author;Description = desc;Version = ver;}
}
步骤3:定义两个用于导出类,它们都实现 ITest
接口,并且应用定义好的 CustMetadataAttribute
来指定元数据。
[Export(typeof(ITest))][CustMetadata("Tom", "debug version", 1)]public class TestWork_V1 : ITest{public void RunTask(){Console.WriteLine("这是版本 1");}}[Export(typeof(ITest))][CustMetadata("Jack", "release version", 2)]public class TestWork_V2 : ITest{public void RunTask(){Console.WriteLine("这是版本 2");}}
步骤4:定义一个新类,用于引用导入的类型与元数据。
public class TestCompos
{[ImportMany]public IEnumerable<Lazy<ITest, IDictionary<string, object>>> ImportedComponents { get; set; }
}
步骤5:加载要查找导出类型的程序集。
Assembly comAss = Assembly.LoadFrom("CustExportProj.dll");
ContainerConfiguration config = new ContainerConfiguration().WithAssembly(comAss);
注意: 此处加载的程序集在.NET Core 2.1中。
步骤6:创建 Composition
容器,并把类型导入到刚定义的 TestCompos
实例中。
TestCompos cps = new TestCompos();
using (var host = config.CreateContainer())
{host.SatisfyImports(cps);
}
步骤7:获取导入的元数据,然后调用导入的类型。
foreach (var c in cps.ImportedComponents)
{ITest obj = c.Value;IDictionary<string, object> meta = c.Metadata;Console.Write("元数据:\n");foreach(var it in meta){Console.Write($"{it.Key}: {it.Value}\n");}Console.Write($"调用 {obj.GetType().Name} 实例:\n");obj.RunTask();Console.Write("\n\n");
}
步骤8:运行应用程序项目,结果如下。
元数据:
Author: Tom
Description: debug version
Version: 1
这是版本 1元数据:
Author: Jack
Description: release version
Version: 2
调用 TestWork_V2 实例:
这是版本 2
总结
本文到这里就结束了,下一篇将介绍应用启动的知识案例。
.net core精彩实例分享 -- 反射与Composition相关推荐
- .net core精彩实例分享 -- 网络编程
文章目录 介绍 具体案例 从Web服务器上下载图片 使用HttpClient类向Web服务器提交数据 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关 ...
- .net core精彩实例分享 -- 面向对象编程
文章目录 介绍 具体案例 指定枚举的基础类型 常量的标识位运算(Enum) 获取枚举中常量的名称 检查枚举实例中是否包含某个标志位 在运行阶段检索特性实例 总结 介绍 随着.net core越来越流行 ...
- .net core精彩实例分享 -- 应用配置和数据库访问
文章目录 介绍 具体案例 自定义环境变量的命名前缀 自定义命令行参数映射 使用JSON文件来配置选项类 在应用程序运行期间创建SQLite数据库 总结 介绍 随着.net core越来越流行,对.ne ...
- .net core精彩实例分享 -- 依赖注入和中间件
文章目录 介绍 具体案例 临时访问服务 以委托形式定义中间件 带参数中间件 IMiddleware中间件的用途 让 HTTP 管道"短路" 中间件的分支映射 文件服务 总结 介绍 ...
- .net core精彩实例分享 -- 应用启动
文章目录 介绍 具体案例 配置Web服务器的URL 配置Web项目的调试方案 基于方法约定的Startup类 使用非预定义环境 总结 介绍 随着.net core越来越流行,对.net core 基础 ...
- .net core精彩实例分享 -- 异步和并行
文章目录 介绍 具体案例 等待线程信号--ManualResetEvent 等待线程信号--AutoResetEvent 多个线程同时写一个文件 串联并行任务 使用Parallel类执行并行操作 为每 ...
- .net core精彩实例分享 -- 序列化
文章目录 介绍 具体案例 将类型实例序列号危机JSON格式 将数据协定序列化为JSON格式 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关的知识也应 ...
- .net core精彩实例分享 -- 文件与I/O
文章目录 介绍 具体案例 创建Zip压缩文件 使用GZipStream类压缩文件 实现本地进程之间的通信 单向管道通信 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了 ...
- .net core精彩实例分享 -- LINQ
文章目录 介绍 具体案例 将对象转为字典集合 将原始序列进行分组 按员工所属部门 DefaultIfEmpty方法的作用 将分组后的序列重新排序 使用并行LINQ 总结 介绍 随着.net core越 ...
最新文章
- R语言ggplot2条形图(bar plot)可视化:更改一个条形(bar)的颜色、突出一个条形(bar)的颜色
- python代码根据当前时间获取下一周的日期
- 【转】mysql-status和variables区别
- VS2017 installed in a different location
- 按钮是什么意思_汽车里的Rear按键是什么意思?
- Redis持久化(转载)
- Android在子线程里使用Toast报错Can't toast on a thread that has not called Looper.prepare()
- Meeters and Greeters 接客大厅
- Atitit.attilax软件研发与项目管理之道
- Google Analytics(分析)网址构建器
- 【win10专业版】如何检测声卡是否损坏
- 一日一技:Python + Excel——飞速处理数据分析与处理
- 【网络安全学习实践】Windows基本DOS文件命令与简易病毒编写
- 0_13_QGIS纠正矢量数据
- html5 游戏制作教程,利用HTML5 Canvas制作一个简单的打飞机游戏
- Java 点击按钮下载Excel
- 用python爬取考研词汇及其近反义词与例句
- 计算机一级胶卷出现文件异常,解决IOS相机胶卷导入照片后堆在最新照片的问题...
- 如何做好企业网络营销推广?从本质上去理解互联网开始!
- 战地3皓月服务器win10系统,战地3配置
热门文章
- grep 判断不是正则的_Shell—正则表达式(grep命令、sed工具)
- 电脑显示没有被指定在上运行_可以桌面显示的便条便签怎么弄?有没有电脑桌面上的便条贴...
- new to python什么意思_Python中__new__的作用
- android iot代码设计,一个简单好用的Android Tab 设计与实现
- swing获取文本框内容_营销管理培训课件,大客服销售策略和技巧,全内容PPT拿来就用...
- java输入输出流_金九银十准备季:Java异常+Java IO与NIO面试题(含答案)
- 求翻转数循环结构C语言,[LeetCode Easy题快一起刷起来] 1. 两数之和 7. 整数翻转
- 京东java笔试_2017阿里,百度,京东java面试+笔试大合集,2018的你会吗?
- 流行趋势-立体感和艺术剪纸风海报美妆设计
- 看完这些美食海报,你是不是又有灵感了?