文章目录

  • 一、使用 Activator.CreateInstance 创建实例
    • 1、使用 Activator.CreateInstance 的优点和缺点
    • 2、使用 Activator.CreateInstance 的代码示例
  • 二、使用 Type.InvokeMember 创建实例
    • 1、使用 Type.InvokeMember 的优点和缺点
    • 2、使用 Type.InvokeMember 的代码示例
  • 三、Activator.CreateInstance 与 Type.InvokeMember 的基准测试
    • 1、反射性能的 BenchmarkDotNet 设置
    • 2、Activator.CreateInstance 与 Type.InvokeMember对比
  • 四、Activator.CreateInstance 与 Type.InvokeMember 总结

一、使用 Activator.CreateInstance 创建实例

在 C# 中,Activator.CreateInstance方法是开发人员通过反射创建实例的最常用方法之一。它允许在运行时创建类的新实例,即使事先不知道它们的具体类型。此方法属于类,System.Activator通常用于需要动态实例化的场景。

的主要目的Activator.CreateInstance是动态创建类的实例 — 因此,如果您在编译时可以访问该类型,那么您可能没有充分的理由使用它!Activator.CreateInstance在要创建的对象类型直到运行时才知道的情况下,例如在加载插件或使用动态加载的程序集时,它特别有用。它消除了对硬编码显式构造函数的需求,并提供了对象创建的灵活性。

1、使用 Activator.CreateInstance 的优点和缺点

Activator.CreateInstance与普通对象实例相比,使用有几个优点:

允许后期绑定对象创建,这在对象类型在运行时可能发生变化的情况下很有用。
可以通过消除使用 switch 语句或 if-else 条件来处理不同对象类型的需求来简化代码库。
提供一种动态且可扩展的对象创建方法。
但是,使用时也需要考虑一些缺点Activator.CreateInstance:

由于运行时解析类型涉及额外的步骤,因此与直接实例化相比,使用反射的性能开销可能更高。
Activator.CreateInstance 通常依赖于公共无参数构造函数的存在。否则,您需要始终知道要传入哪些参数 — 如果您要针对许多不同类型动态执行此操作,则具有挑战性。
由于没有编译时检查签名兼容性,因此在修改目标类型时容易出现错误。

2、使用 Activator.CreateInstance 的代码示例

以下代码示例演示了如何使用Activator.CreateInstance动态创建实例:

// Example 1: Creating an instance of a known type
Type objectType = typeof(MyClass);
object instance = Activator.CreateInstance(objectType);

在示例 1 中,我们使用typeof获取Type代表已知类的对象MyClass。然后,我们使用Activator.CreateInstance创建 的新实例MyClass。

// Example 2: Creating an instance of an unknown type at runtime
string typeName = "MyNamespace.MyClass";
Type unknownType = Type.GetType(typeName);
object dynamicInstance = Activator.CreateInstance(unknownType);

在示例 2 中,我们有一个由字符串表示的未知类型typeName。我们使用根据提供的类型名称Type.GetType获取对象。最后,用于创建动态确定类型的新实例。TypeActivator.CreateInstance

我们再看一个例子,其中我们可以为构造函数传递参数——同样,假设我们知道签名,因为我们无法在编译时通过这种方法证明它:

// Example 3: Creating an instance with constructor parameters:
string typeName = "MyNamespace.MyClass";
Type unknownType = Type.GetType(typeName);
Object dynamicInstance = Activator.CreateInstance(
    unknownType,
    new[]
    {
        "Hello World!", //  this is the single string parameter!
    });

二、使用 Type.InvokeMember 创建实例

Type.InvokeMember是 DotNet 中 Reflection 提供的一种可用方法,允许我们动态创建类型的实例。它提供了一种灵活的方法,可以通过利用手头类型的信息在运行时实例化对象。出于这些原因,它在如何利用它来创建对象实例方面非常相似。

1、使用 Type.InvokeMember 的优点和缺点

Type.InvokeMember与普通对象实例相比,使用以下方法具有一些普遍的优点:

允许后期绑定对象创建,这在对象类型在运行时可能发生变化的情况下很有用。
可以通过消除使用 switch 语句或 if-else 条件来处理不同对象类型的需求来简化代码库。
提供一种动态且可扩展的对象创建方法。
等一下……这不就是我们上面看到的那个列表吗Activator.CreateInstance?没错。所以我们就简短地介绍一下这部分。除非我们开始研究性能,否则我们不会看到任何大的差异——也许在某些非常特殊的边缘情况下。但总的来说,两者都提供了非常全面的方法来动态实例化对象,而 InvokeMember 有点冗长,因为它处理的不仅仅是构造函数。

在进行基准测试之前,我们先检查一些代码。

2、使用 Type.InvokeMember 的代码示例

下面是一个示例代码片段,演示了如何Type.InvokeMember动态创建类型实例:

// Example 1: Creating an instance of a known type
Type objectType = typeof(MyClass);
var instance = objectType
.InvokeMember(
    null,
    BindingFlags.CreateInstance,
    null,
    null,
    null);

在上面的例子中,我们首先获取代表类“MyClass”的 Type 对象。然后我们使用 Type.InvokeMember 创建该类的实例并将其分配给“instance”变量。这使我们能够动态创建“MyClass”的对象,而无需明确指定类名。

而在编译时不知道类的情况下执行此操作与以前非常相似。但这部分与 InvokeMember 无关:

// Example 2: Creating an instance of an unknown type at runtime
string typeName = "MyNamespace.MyClass";
Type unknownType = Type.GetType(typeName);
var instance = objectType.InvokeMember(
    null,
    BindingFlags.CreateInstance,
    null,
    null,
    null);

最后,如果我们需要传递一些构造函数参数,那么我们也可以这样做:

// Example 3: Creating an instance with constructor parameters:
string typeName = "MyNamespace.MyClass";
Type unknownType = Type.GetType(typeName);
var instance = objectType.InvokeMember(
    null,
    BindingFlags.CreateInstance,
    null,
    null,
    new[]
    {
        "Hello World!",
    });

三、Activator.CreateInstance 与 Type.InvokeMember 的基准测试

1、反射性能的 BenchmarkDotNet 设置

我认为我们可以在三种情况下使用 BenchmarkDotNet 运行基准测试:

无参数构造函数类
具有单个字符串参数的构造函数
具有单个字符串参数的主构造函数
我想加入主构造函数,因为我知道这个特性会招致很多人的反对——最好还是获取一些数据吧!以下是我们将要实例化的类,供参考:

public class ParameterlessClass
{
}

public class ClassicStringParameterClass
{
    private readonly string _value;

    public ClassicStringParameterClass(string value)
    {
        _value = value;
    }
}

public class PrimaryConstructorStringParameterClass(
    string _value)
{
}

至于基准测试,让我们看看我们将要运行的以下类。请记住,我使用它Activator.CreateInstance作为基线是因为我想比较Activator.CreateInstancevs Type.InvokeMember— 我只包括正常的构造函数路径作为参考。您也可以在 GitHub 上找到所有这些代码:

[ShortRunJob]
public class ParameterlessClassBenchmarks
{
private Type? _type;
[GlobalSetup]
public void GlobalSetup()
{
_type = typeof(ParameterlessClass);
}
[Benchmark]
public void Constructor()
{
var instance = new ParameterlessClass();
}
[Benchmark(Baseline = true)]
public void Activator_Create_Instance()
{
var instance = Activator.CreateInstance(_type!);
}
[Benchmark]
public void Type_Invoke_Member()
{
var instance = _type!.InvokeMember(
null,
BindingFlags.CreateInstance,
null,
null,
null);
}
}
[ShortRunJob]
public class ClassicStringParameterClassBenchmarks
{
private Type? _type;
[GlobalSetup]
public void GlobalSetup()
{
_type = typeof(ClassicStringParameterClass);
}
[Benchmark]
public void Constructor()
{
var instance = new ClassicStringParameterClass("Hello World!");
}
[Benchmark(Baseline = true)]
public void Activator_Create_Instance()
{
var instance = Activator.CreateInstance(
_type!,
new[]
{
"Hello World!",
});
}
[Benchmark]
public void Type_Invoke_Member()
{
var instance = _type!
.InvokeMember(
null,
BindingFlags.CreateInstance,
null,
null,
new[]
{
"Hello World!",
});
}
}
[ShortRunJob]
public class PrimaryConstructorStringParameterClassBenchmarks
{
private Type? _type;
[GlobalSetup]
public void GlobalSetup()
{
_type = typeof(PrimaryConstructorStringParameterClass);
}
[Benchmark]
public void Constructor()
{
var instance = new PrimaryConstructorStringParameterClass("Hello World!");
}
[Benchmark(Baseline = true)]
public void Activator_Create_Instance()
{
var instance = Activator.CreateInstance(
_type!,
new[]
{
"Hello World!",
});
}
[Benchmark]
public void Type_Invoke_Member()
{
var instance = _type!
.InvokeMember(
null,
BindingFlags.CreateInstance,
null,
null,
new[]
{
"Hello World!",
});
}
}

2、Activator.CreateInstance 与 Type.InvokeMember对比

当我们将这两种反射方法进行对比时,获胜者是…视情况而定。在最常见的一种情况下,我会说有一个非常明显的赢家,但对于其他情况,它们非常接近。但请务必阅读结论,因为这不是故事的结局。

我们要研究的第一个基准是无参数构造函数:
Activator.CreateInstance 与 Type.InvokeMember的区别插图

Activator.CreateInstance 与 Type.InvokeMember – 无参数构造函数的基准
这里明显胜出:Activator.CreateInstance几乎高出一个数量级。如果你的构造函数没有任何参数,那么最好的选择就是这个。

接下来,让我们检查Activator.CreateInstance一下Type.InvokeMember接受单个字符串参数的构造函数:
Activator.CreateInstance 与 Type.InvokeMember的区别插图(1)

Activator.CreateInstance 与 Type.InvokeMember – 带参数的经典构造函数的基准
这两款车基本上势均力敌,尽管Activator.CreateInstance略胜一筹,但差距几乎可以忽略不计。

要查看的最后一个场景是主构造函数,在本例中,主构造函数接受一个字符串参数:
Activator.CreateInstance 与 Type.InvokeMember的区别插图(2)

Activator.CreateInstance 与 Type.InvokeMember – 主构造函数的基准
获胜者:Type.InvokeMember,但只领先一点点。非常有趣的是,这与我们在之前的基准测试中看到的结果相反!

四、Activator.CreateInstance 与 Type.InvokeMember 总结

当谈到Activator.CreateInstancevs的性能结果时Type.InvokeMember,无参数构造函数的情况明显胜出:Activator.CreateInstance。但是当我们开始需要参数或使用带参数的主构造函数时,它开始均衡甚至有利于Type.InvokeMember。

本站无任何商业行为
个人在线分享 » Activator.CreateInstance 与 Type.InvokeMember的区别
E-->