文章目录

  • 介绍
  • 具体案例
    • 用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相关推荐

  1. .net core精彩实例分享 -- 网络编程

    文章目录 介绍 具体案例 从Web服务器上下载图片 使用HttpClient类向Web服务器提交数据 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关 ...

  2. .net core精彩实例分享 -- 面向对象编程

    文章目录 介绍 具体案例 指定枚举的基础类型 常量的标识位运算(Enum) 获取枚举中常量的名称 检查枚举实例中是否包含某个标志位 在运行阶段检索特性实例 总结 介绍 随着.net core越来越流行 ...

  3. .net core精彩实例分享 -- 应用配置和数据库访问

    文章目录 介绍 具体案例 自定义环境变量的命名前缀 自定义命令行参数映射 使用JSON文件来配置选项类 在应用程序运行期间创建SQLite数据库 总结 介绍 随着.net core越来越流行,对.ne ...

  4. .net core精彩实例分享 -- 依赖注入和中间件

    文章目录 介绍 具体案例 临时访问服务 以委托形式定义中间件 带参数中间件 IMiddleware中间件的用途 让 HTTP 管道"短路" 中间件的分支映射 文件服务 总结 介绍 ...

  5. .net core精彩实例分享 -- 应用启动

    文章目录 介绍 具体案例 配置Web服务器的URL 配置Web项目的调试方案 基于方法约定的Startup类 使用非预定义环境 总结 介绍 随着.net core越来越流行,对.net core 基础 ...

  6. .net core精彩实例分享 -- 异步和并行

    文章目录 介绍 具体案例 等待线程信号--ManualResetEvent 等待线程信号--AutoResetEvent 多个线程同时写一个文件 串联并行任务 使用Parallel类执行并行操作 为每 ...

  7. .net core精彩实例分享 -- 序列化

    文章目录 介绍 具体案例 将类型实例序列号危机JSON格式 将数据协定序列化为JSON格式 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关的知识也应 ...

  8. .net core精彩实例分享 -- 文件与I/O

    文章目录 介绍 具体案例 创建Zip压缩文件 使用GZipStream类压缩文件 实现本地进程之间的通信 单向管道通信 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了 ...

  9. .net core精彩实例分享 -- LINQ

    文章目录 介绍 具体案例 将对象转为字典集合 将原始序列进行分组 按员工所属部门 DefaultIfEmpty方法的作用 将分组后的序列重新排序 使用并行LINQ 总结 介绍 随着.net core越 ...

最新文章

  1. R语言ggplot2条形图(bar plot)可视化:更改一个条形(bar)的颜色、突出一个条形(bar)的颜色
  2. python代码根据当前时间获取下一周的日期
  3. 【转】mysql-status和variables区别
  4. VS2017 installed in a different location
  5. 按钮是什么意思_汽车里的Rear按键是什么意思?
  6. Redis持久化(转载)
  7. Android在子线程里使用Toast报错Can't toast on a thread that has not called Looper.prepare()
  8. Meeters and Greeters 接客大厅
  9. Atitit.attilax软件研发与项目管理之道
  10. Google Analytics(分析)网址构建器
  11. 【win10专业版】如何检测声卡是否损坏
  12. 一日一技:Python + Excel——飞速处理数据分析与处理
  13. 【网络安全学习实践】Windows基本DOS文件命令与简易病毒编写
  14. 0_13_QGIS纠正矢量数据
  15. html5 游戏制作教程,利用HTML5 Canvas制作一个简单的打飞机游戏
  16. Java 点击按钮下载Excel
  17. 用python爬取考研词汇及其近反义词与例句
  18. 计算机一级胶卷出现文件异常,解决IOS相机胶卷导入照片后堆在最新照片的问题...
  19. 如何做好企业网络营销推广?从本质上去理解互联网开始!
  20. 战地3皓月服务器win10系统,战地3配置

热门文章

  1. grep 判断不是正则的_Shell—正则表达式(grep命令、sed工具)
  2. 电脑显示没有被指定在上运行_可以桌面显示的便条便签怎么弄?有没有电脑桌面上的便条贴...
  3. new to python什么意思_Python中__new__的作用
  4. android iot代码设计,一个简单好用的Android Tab 设计与实现
  5. swing获取文本框内容_营销管理培训课件,大客服销售策略和技巧,全内容PPT拿来就用...
  6. java输入输出流_金九银十准备季:Java异常+Java IO与NIO面试题(含答案)
  7. 求翻转数循环结构C语言,[LeetCode Easy题快一起刷起来] 1. 两数之和 7. 整数翻转
  8. 京东java笔试_2017阿里,百度,京东java面试+笔试大合集,2018的你会吗?
  9. 流行趋势-立体感和艺术剪纸风海报美妆设计
  10. 看完这些美食海报,你是不是又有灵感了?