一个实例方法的'this'参数可以是未定义类型的吗(例如System.Object)?

4

我正在使用System.Reflection.EmitTypeBuilder来发出一堆具有实例方法的自定义.NET类。例如:

public class EmittedClass
{
    public bool TryGetName(out string value)
    {
        ...
    }

    public bool TryGetAge(out int value)
    {
        ...
    }
}

所有方法都遵循相同的签名,可以用通用委托描述:

public delegate bool TryGetter<T>(out T value);

当然,我希望能够在调用时明确指定目标实例,就像这样:
var instance = InstanceFactory.CreateInstance();
var tryGetName = InstanceFactory.CreateTryGetter<string>("Name");
string name;
if (tryGetName(instance, out name)) // Problem here.
{
    ...
}

为了使这个工作起来,我需要将委托变成所谓的“开放委托”:
public delegate bool TryGetter<T>(object instance, out T value);

由于我没有目标实例的编译时类型,所以需要将其作为 System.Object 传递。但是,在运行时会出现错误,因为实例方法期望它的 'this' 是声明类类型的。这很糟糕。
我目前使用的解决方法是创建一个中间 lambda 表达式,该表达式接受输入对象,对其进行运行时转换为目标类型,然后继续调用目标方法。这样做可以解决问题,但是我对在中间添加这个修补程序感到不安。
问题是:是否可以以某种方式更改发出的方法,使其接受任何 System.Object 作为其 'this' 参数,并仍保持我的发出代码可验证?
如果不能,我认为仍然可以通过使方法静态化并在它们的主体中执行转换来避免中间 lambda。但是理想情况下,我希望完全避免强制转换,但我怀疑由于 CIL 验证和元数据加载工作的方式,这在托管世界中是无法实现的。
只是想知道对 CIL 更有了解的人能否就此向我提供建议。谢谢!
更新:我要解决的问题
类具有属性,通常由字段支持。如果预计该类的实例仅维护一次状态,则这是可以的。状态指给定时间内实例字段中值的组合。
我的业务需求是实现一个备选存储,以使单个类实例在同一时间具有多个状态。该要求的一个附带目标是尽可能地高效,无论是内存还是访问速度方面。
我方法的核心思想是使用 System.Reflection.Emit 创建业务类的镜像,并使业务类维护一组这些实例,每个实例对应于特定状态。然后,业务类的 getter 和 setter 自然地必须连接到状态实例上的适当方法。涉及更多细节,但这是核心思想。
我希望这种解释有助于理解我提出这个问题的原因。我知道这可能看起来过度工程化,但其他不涉及 System.Reflection.Emit 的替代方法太慢且资源占用过高。我不能接受这样的情况。

我认为你正在做一些非常有趣的事情。然而,你的问题问反了。与其问一个坏解决方案是否可行,不如问一个好的解决方案将会产生更多有用的回答,以我个人的看法。 - sehe
明白了,我没有解释清楚我要解决的问题。我会更新我的问题。 - aoven
@Dan:嗯,它们确实是两个不同的对象,对吧?一个是业务对象,另一个是状态对象。但我知道你的意思。:) 其中一个原因是业务对象还有其他属性(更不用说很多方法)与状态管理无关。因此,简单地重用相同的类来处理状态并不可行。第二个原因是业务对象的使用者不应该被多个对象所拖累。对他来说,这确实只是一个对象。最后一个原因是为了避免手动编写样板代码。 - aoven
@aoven:你在编辑中说:“我的业务需求是实现一种替代存储方式,使单个类实例能够同时具有多个状态。”这不是业务需求,而是对业务需求的技术解决方案。我无法想象为什么会更喜欢大量的System.Reflection.Emit代码而不是任意数量的样板代码-你确定这是正确的答案吗? - Dan Puzey
@Aoven:仅仅因为你的目标受众是开发人员,并不意味着你需要一个复杂的过度设计的解决方案。你说你看到了很多相似之处,听起来你试图提供一个单一的技术解决方案。然而,这个解决方案听起来比原始问题要复杂得多。出于某种原因,架构师似乎喜欢这样做,我还没有想明白为什么。.Net中的安全措施是有原因的,那就是为了防止常见的错误导致程序失败。要么接受它们,要么选择一个给你更多灵活性的平台。 - Andy
显示剩余6条评论
4个回答

2

您不需要在委托签名中表示'this'类型。

假设有一个委托定义如下:

public delegate bool TryGetter<T>(out T value);

任何形如:

TryGetter<T> x;

可以包含以下任一内容:

  1. 具有签名 bool Foo(out T value) 的类型R上的实例方法,以及类型为R的对象实例。
  2. 具有签名 static bool Foo(out T value) 的静态方法。
  3. 具有签名 static bool Foo(R object, out T value) 的静态方法,在给定类型R的对象实例的情况下使用。

第三种形式被称为委托柯里化,并允许具有N个参数的静态方法表现得像具有N个参数的实例方法(但仅可对第一个参数进行柯里化)。

因此,您需要的接口是:

var instance = InstanceFactory.CreateInstance();
var tryGetName = InstanceFactory.CreateTryGetter<string>(instance,"Name");

然后你可以执行:tryGetName() 来返回该值。

你可能想选择第三种情况,生成一个具有签名 bool TryGetWhatEver(TheTypeOfInstance obj, out WhatEver x)DynamicMethod,然后创建一个TryGetter<WhatEver>


然而,我仍然好奇为什么需要这样做。除非你正在动态生成应用程序的大块内容(例如Rails),否则这似乎会过于复杂。


是的,我们肯定在这里谈论大块头。 :) - aoven

2

考虑到您的要求,像ValueInjecterAutoMapper这样的库是否能够减少从一个大型业务对象复制到不同状态对象的所有样板代码? 即使它不完全符合您的要求,也许它们可以为您的任务提供一些灵感。


那些库解决的是不同的高级问题,恐怕与我想要的正好相反:我试图将数值保留在一个地方(状态对象)并从其他地方(例如业务对象)跳转到它们。无论如何,还是谢谢! - aoven

1

Reflection.Emit对于创建内部存储机制以容纳属性值副本来说,实在是过于复杂了。使用一些简单的字典就足以存储各种属性名称-值映射的不同“状态”。


当然。那正是我的最初解决方案。我还清楚地记得当时的想法:“我知道字典在内存方面有点浪费。但它会有多糟糕呢?”结果证明它可能非常糟糕。每种类型的两个字典,平均每个业务对象实例三种类型,再加上典型集合中约一千个左右的业务对象,总共超过1 MB的内存。考虑到所有其他在业务应用程序中(合理地)浪费内存的事情,这完全是不可接受的。此外,由于装箱,性能的大部分直接消失了。 - aoven

0

只需使用 dynamic

dynamic instance = InstanceFactory.CreateInstance();
var tryGetName = InstanceFactory.CreateTryGetter<string>("Name");
string name;

// Should work if “instance” is of the right type *at runtime*
if (tryGetName(instance, out name))
{
    ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接