随着.net core
越来越流行,对.net core
基础知识的了解,实际应用等相关的知识也应该有所了解。所以就有了这篇文章,案例都是来自阅读的书籍,或者实际工作中感觉比较有用的应用。分享亦总结。
本文主要介绍 .net core
相关的反射与Composition案例。
【导语】
除了通过 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:运行应用程序项目,结果如下。
【导语】
CustomAttributeExtensions
类提供了一系列扩展方法,可以获取程序集、类型、类型成员、参数上应用的自定义特性(Attribute
)实例。
如果实现知道特性的类型,则可以使用带泛型参数的方法:
T GetCustomAttribute(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();
// 输出特性类的属性值
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("gen_pl");
IPlayer p2 = host.GetExport("pro_pl");
}
在调用 GetExport
方法时,需要传递每个导出类型所对应的协定名称。
步骤9:分别调用两个对象实例的 PlayTracks
方法。
using(CompositionHost host = config.CreateContainer())
{
...
p1.PlayTracks();
p2.PlayTracks();
}
步骤10:运行应用程序项目,结果如下。
【导语】
Composition
技术支持将类型导入某个类的属性(或方法参数)中。应用了 ImportAttribute
的属性只可以导入单个类型实例,而应用了 ImportManyAttribute
的属性则可以导入多个类型实例,此时属性一般声明为 IEnumerator
类型,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 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
类型的对象来接受导入的类型与元数据。Lazy
类提供了一种机制————类型可以延迟初始化,即当 Value
属性被访问时才会调用T类型的构造器。TMetadata
表示导入的元数据,一般情况下,可以使用 IDictionary
类型来接收元数据,也可以使用一个自定义的类来接收(带无参数构造函数的类)。
导入的元数据,不仅仅可以使用 IDictionary
类型来接收,还可以使用自定义的类来接收,此自定义类需要满足两个条件:
(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>> 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 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
本文到这里就结束了,下一篇将介绍应用启动的知识案例。