.net core精彩实例分享 -- 反射与Composition

介绍

随着.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

总结

本文到这里就结束了,下一篇将介绍应用启动的知识案例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值