【问题标题】:Can 'this' argument of an instance method be untyped (i.e. System.Object)?实例方法的“this”参数可以无类型(即 System.Object)吗?
【发布时间】:2011-04-26 13:43:36
【问题描述】:

我正在使用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 表达式,该表达式接受输入对象,执行运行时转换为目标类型,然后继续调用目标方法。它有效,但我对中间有这个杂物感到不安。

问题是:我能否以某种方式更改我发出的方法,以便它们的 this 参数将接受任何 System.Object 并仍然保持我发出的代码可验证?

如果不是,我想我仍然可以通过将方法设为静态并在它们的主体中执行强制转换来避免中间 lambda。但是,理想情况下,我想完全避免强制转换,但我怀疑由于 CIL 验证和元数据加载的工作方式,这在托管世界中是站不住脚的。

只是想知道是否有更了解 CIL 的人可以就这个主题给我建议。谢谢!

更新:我要解决的问题

类具有属性,通常由字段支持。这很好,如果期望所述类的实例一次只保持一个状态。状态是指给定时间实例字段中值的组合。

我的业务需求是为普通字段实现一种替代存储,这将使单个类实例能够同时具有多个状态。此要求的一个附带目标是使其尽可能高效,无论是内存还是访问速度。

我的方法背后的核心思想是使用System.Reflection.Emit 创建业务类的镜像,并让业务类维护一组这些实例,每个实例对应于一个给定的状态。业务类的 getter 和 setter 自然必须连接到状态实例上的适当方法。涉及的细节很多,但这是核心思想。

我希望解释有助于理解我提出这个问题的原因。我很欣赏这对许多人来说似乎是过度设计,但其他不涉及System.Reflection.Emit 的替代方案实在是太慢了,而且资源匮乏。我不能拥有那个。

【问题讨论】:

  • 我认为您正在做一些非常有趣的事情。但是,您是在向后问问题。恕我直言,与其询问是否可以制定一个糟糕的解决方案,不如寻求一个好的解决方案会产生更多有用的回应
  • 点了。我没有解释问题,我正在努力解决。我会更新我的问题。
  • 回应您的编辑:您的实际业务问题是什么?为什么需要一个对象同时具有两种状态 - 为什么不是两个不同的对象?
  • @Dan:嗯,它们两个不同的对象,不是吗?一个是业务对象,另一个是状态对象。但我知道你的意思。 :) 部分原因是业务对象还具有与状态管理无关的其他属性(更不用说很多方法)。为此,简单地为状态重用相同的类不是一种选择。第二个原因是业务对象的消费者不应该被多个对象所负担。这对他来说真的只是一个对象。最后一部分原因是为了避免手动样板代码。
  • @aoven:您在编辑中说过“我的业务需求是实现一种替代存储来替代普通字段,这将使单个类实例能够同时具有多个状态。”这不是业务需求,而是针对业务需求的技术解决方案。我无法想象有理由更喜欢大量 System.Reflection.Emit 代码而不是任何数量的样板代码 - 你确定这是正确的答案吗?

标签: c# cil reflection.emit


【解决方案1】:

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

给定的委托定义如下:

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

任何形式的变量:

TryGetter<T> x;

可以同时持有:

  1. 类型 R 上的实例方法,签名为 bool Foo(out T value),以及类型 R 的对象实例。
  2. 带有签名static bool Foo(out T value)的静态方法
  3. 一个带有签名static bool Foo(R object, out T value) 的静态方法,给定一个类型为 R 的对象实例。

第三种形式称为委托柯里化,它允许具有 N+1 个参数的静态方法表现得好像它是具有 N 个参数的实例方法(但是只有第一个参数可以被柯里化)。

那么,你想要的界面是:

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

然后你可以这样做:tryGetName() 来返回值。

您可能希望使用案例 #3,在该案例中生成带有签名 bool TryGetWhatEver(TheTypeOfInstance obj, out WhatEver x)DynamicMethod,然后创建 TryGetter&lt;WhatEver&gt;


但是,我仍然很好奇您为什么需要这样做。除非您动态生成应用程序的大块(如 rails),否则这似乎过于复杂。

【讨论】:

  • 是的。我们肯定在这里谈论大块。 :)
【解决方案2】:

考虑到您的需求,像ValueInjecterAutoMapper 这样的库不会减少从一个大型业务对象复制到不同状态对象的所有样板代码吗?即使这不是您想要的,也可能对您的任务有所启发。

【讨论】:

  • 恐怕这些库正在解决一个不同的高级问题。如果有的话,我实际上是在对象之间复制值的确切相反:我试图将它们保存在一个地方(状态对象)并从其他地方重定向到它们(业务对象,一)。还是谢谢!
【解决方案3】:

Reflection.Emit 对于创建内部存储机制来存放您的属性值的副本来说太过分了。像几个 Dictionary 这样简单的东西就足以存储 propertyname->value 映射的各种“状态”。

【讨论】:

  • 当然。这正是我最初的解决方案。我当时的想法记得很清楚:“我知道字典在记忆方面有点浪费。但它有多糟糕呢?”事实证明,情况可能非常糟糕。每个类型的两个字典乘以每个业务对象实例的三个类型平均乘以一个典型集合中的一千个左右的业务对象,总内存远远超过 1 MB。考虑到在商业应用程序中(合法地)浪费内存的所有其他事情,这是完全不可接受的。此外,由于拳击,很大一部分表演直接被淘汰了。
【解决方案4】:

只需使用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))
{
    ...
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多