【问题标题】:How to call a method with a generic constraint using a type parameter without the constraint?如何使用没有约束的类型参数调用具有泛型约束的方法?
【发布时间】:2018-11-23 09:37:04
【问题描述】:

假设我有一个方法:

public void ExampleMethod<T>(T x) where T : struct // or new()
{
    // example usage, similar to what's actually happening
    var z = (T)Enum.Parse(typeof(T), privateFieldOfTypeString);

    // do something depending on values of x and z
    // possibly using other methods that require them being enums

    // or in case of the new() constraint:
    // var t = new T() and do something together with x
}

我想按如下方式使用它:

public void CallerMethod<S>(S y)
{
    if (typeof(S).IsEnum) // or some other invariant meaning that `S` is "new()able"
    {
        ExampleMethod(y); // won't compile
    }
}

所以,在运行时我知道S 满足ExampleMethod&lt;T&gt; 的约束。我知道可以使用反射来调用它,类似于:

this
    .GetType()
    .GetMethod(nameof(ExampleMethod<object>))
    .MakeGenericMethod(typeof(S))
    .Invoke(this, new object[] { y });

没有反射可以吗?

注意:这是来自真实示例的简化代码,显然我无法控制这些方法的签名,因此答案是“将约束添加到 CallerMethod”和“从ExampleMethod中删除约束是无效的。

是的,整个事情都应该重新设计,这样整个问题就不会出现了。但在现实生活中,“整个事情”往往太大、太耦合、太冒险而无法重写。一些需求以一种意想不到的方式发生了变化 - 因此明显的代码异味,我试图通过将其隐藏在一个看起来很讨厌的地方来最小化它。

【问题讨论】:

  • 知道S满足ExampleMethod&lt;T&gt;的约束,但编译器不满足。如果可能,您需要将约束添加到CallerMethod。这就是泛型成员的本质。
  • @Nkosi 继续你的表述:问题是“如何告知编译器这个事实”。泛型之外的一个类比是强制转换 - 您告诉编译器您知道特定变量属于特定类型。
  • 强制转换是运行时
  • @BartoszKP 对于这两种方法如何相互交互的简化示例,我不知所措。是否可以提供一些上下文来了解预期目标,以便将其排除为XY problem
  • @Nkosi 我不这么认为。细节无关紧要,这就是我坚持使用这两种方法的事实,我需要从另一种方法中调用一个。它们位于不同的类型和不同的组件上,但这些细节确实无关紧要。我完全知道整个事情应该重新设计,这样这个问题就不会出现了。恕我直言,这个问题很清楚 - 是否有可能,如果是的话 - 如何?可能的预期答案很容易想象:“不,不可能,受语言规则/编译器限制”或“是的,有可能,就是这样”。

标签: c# generics generic-constraints


【解决方案1】:

你可以使用dynamic:

if (typeof(S).IsEnum)
{
    ExampleMethod((dynamic)y);
}

【讨论】:

  • 这当然只是一种隐藏反射类活动的好方法
  • 谢谢,没想到。尽管@Damien_The_Unbeliever 说了什么:)
【解决方案2】:

您可以利用运算符重载;定义CallerMethod 的多个显式版本,它们都可以成功地对ExampleMethod 进行后续调用

CallerMethod(Enum1 value) { ExampleMethod(value); }
CallerMethod(Enum2 value) { ExampleMethod(value); }
CallerMethod(Enum3 value) { ExampleMethod(value); }

等等

如果有大量增长类型需要CallerMethod 的版本,您可以编写T4 template 来生成具有所有实现的partial 类。

【讨论】:

    【解决方案3】:

    如果枚举类型是已知的,一种可能性(虽然很冗长)是转换为已知类型。

    例如,作为起点,

    public void CallerMethod<S>(S y) {
        if (typeof(S).IsEnum) {
            if (y is KnownEnum) {
                var z = (KnownEnum)Enum.Parse(typeof(S), y.ToString());
                ExampleMethod(z);
            }
            if (y is KnownEnum2) {
                var z = (KnownEnum2)Enum.Parse(typeof(S), y.ToString());
                ExampleMethod(z);
            }
            //...
        }
    }
    

    【讨论】:

    • 会工作的,谢谢。但是,当枚举数量很大/未知/可能发生变化时,这种方法是不可维护的。
    • 我的想法也是如此。寻找替代品
    【解决方案4】:

    在您的特定情况下,您可以只转换为 int。

    public void CallerMethod<S>(S y)
    {
        if (typeof(S).IsEnum)
        {
            ExampleMethod((int)y);
        }
    }
    

    【讨论】:

    • 那也不会编译Cannot convert type 'S' to 'int'
    • 并非每个枚举都使用 int 作为其基础类型。另外,如果ExampleMethod 根据实际枚举类型做事(例如在参数上调用ToString())怎么办?转换为 int 时,您将丢失此类型信息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-02
    • 2011-06-23
    相关资源
    最近更新 更多