【问题标题】:Inferring generics from parent class in method signature fails从方法签名中的父类推断泛型失败
【发布时间】:2020-12-29 12:17:27
【问题描述】:

我正在使用一个库,其中包含(数百个模板生成的)没有泛型修饰符的类,每个类都使用泛型修饰符扩展相同的类(本质上是为了缩短符号)。例如

class DArr : NumberObject<double, MyArrayIndexer> { ... }
class IArr : NumberObject<int, MyArrayIndexer> { ... }
class DMat : NumberObject<double, MyMatrixIndexer> { ... }
class IMat : NumberObject<int, MyMatrixIndexer> { ... }
class BMat : NumberObject<bool, MyMatrixIndexer> { ... }
and so on

我现在想编写一个函数来处理这些函数,该函数需要它们的内部类型(例如,需要知道每个元素是 4 字节还是 8 字节,以及正在使用什么类型的索引器的内部副本)。因此,我对我的函数进行了签名:

public SomeUnrelatedClass<T> Process<T,TVal,T0>(SomeUnrelatedClass<T> obj) where T : NumberObject<TVal,T0> {
 //here some tstuff that requires T, TVal and T0
} 

但不幸的是,我似乎无法以我需要的方式使用它,即

SomeUnrelatedClass<DArr> input = ...;
SomeUnrelatedClass<DArr> output = Process(input);

因为它无法从(为什么??)推导出 TVal 和 T0,尽管它是唯一确定的。我该如何补救?将其称为SomeUnrelatedClass&lt;DArr&gt; output = Process&lt;DArr,double,MyArrayIndexer&gt;(input); 不是一种选择,因为实际上这些泛型有很多而且它们的名称也更长(而且这个函数将在未来的代码中使用数千次,所以我宁愿现在编写一个更复杂的函数,使其正确并保持语法简单)。理想情况下,我不会做一长串“if type == ...”,因为类型列表是模板生成的,并且随着类型的添加会随着时间的推移而变化(这意味着这个模板将依赖于另一个模板,等等)

我想到了一些愚蠢的技巧,比如制作语法Process&lt;T,TVal,T0&gt;(SomeUnrelatedClass&lt;T&gt; obj,SomeUnrelatedClass&lt;NumberObject&lt;TVal,T0&gt;&gt; sameObj) 和调用SomeUnrelatedClass&lt;DArr&gt; output = Process(input,input);,但我觉得这只是一个非常糟糕的解决方案。有什么更“正确”的方法来做到这一点?

【问题讨论】:

  • 我知道支持这一点的语言,但 C# 不是其中之一。您将不得不考虑变通办法。
  • 你想得到doubleMyArrayIndexer的字符串class DArr : NumberObject&lt;double, MyArrayIndexer&gt;
  • 我已经向 ahybertz 答案添加了一条评论,它可以为您提供您想要的。试试这个签名:public static SomeUnrelatedClass&lt;T&gt; Process&lt;T, TVal, T0&gt;(T input) where T : NumberObject&lt;TVal, T0&gt;.
  • 你能把Process方法移到SomeUnrelatedClass类吗?因为它显然知道所有类型是什么。

标签: c# generics inheritance


【解决方案1】:

我认为您在这里可能会走运一些,因为 C# 不会根据您指定的约束来推断类型。我认为这是规范的一部分,从我可以挖掘出来的。但无论如何你也许能够解决你的问题:) - 我设法让以下运行。

{
    var input = new DArr(12.34);
    var output = Visitor.Process(input);
    Console.WriteLine($"Type [{output.Obj.GetType()}] with value {output.Obj.MyType}");
}
{
    var input = new IArr(42);
    var output = Visitor.Process(input);
    Console.WriteLine($"Type [{output.Obj.GetType()}] with value {output.Obj.MyType}");
}

// Generated output:
//
// Type [DArr] with value 12,34
// Type [IArr] with value 42

因此,无论您定义了多少类型,Process 方法都会消耗输入并将输入映射到正确的具体类型。

以下是它的工作原理:

public class NumberObject<TType, TIndexer>
{
    public NumberObject(TType type) { MyType = type; }
    public TType MyType { get; }
}

public class MyArrayIndexer { }

public class DArr : NumberObject<double, MyArrayIndexer>
{
    public DArr(double value) : base(value) { }
}

public class IArr : NumberObject<int, MyArrayIndexer>
{
    public IArr(int value) : base(value) { }
}

public class SomeUnrelatedClass<T>
{
    public T Obj { get; }

    public SomeUnrelatedClass(T obj){ Obj = obj; }
}

public class Visitor
{
    public static SomeUnrelatedClass<NumberObject<TVal, T0>> Process<TVal, T0>(NumberObject<TVal, T0> input)
    {
        return new SomeUnrelatedClass<NumberObject<TVal, T0>>(input);
    }
}

这里的技巧是减少泛型参数的数量 - 去掉 T 类型,因为它可以直接由剩下的两个表示,因为你实际上也写了自己

T is NumberObject<TVal, T0>

当要推断的类型级别降低时,C# 编译器可以直接确定它们,viola 您可以让您为 Process 方法指定泛型参数。

【讨论】:

  • 但这会返回一个SomeUnrelatedClass&lt;NumberObject&lt;TVal,T0&gt;&gt; out,而不是SomeUnrelatedClass&lt;T&gt;,不是吗?
  • 不,它会返回原始类型 - 就像我在生成的输出中使用上面的 GetType() 调用显示的那样
  • 那么如何使用它呢?当我复制粘贴您的代码并执行 SomeUnrelatedClass&lt;DArr&gt; x = null; var y = Visitor.Process(x); 时,它抱怨无法导出 TVal 和 T0 的类型。
  • 您在示例中使用它 - 看看 SomeUnrelatedClass 是如何实现的。它将原始类型存储为属性 Obj 并且您可以通过这种方式访问​​它。我的建议假设您可以访问 SomeUnrelatedClass 并且可以进行所需的更改。
  • @ahybertz - 如果您将Process 的签名更改为public static SomeUnrelatedClass&lt;T&gt; Process&lt;T, TVal, T0&gt;(T input) where T : NumberObject&lt;TVal, T0&gt;,那么您就可以为 OP 提供他们想要的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-15
  • 1970-01-01
  • 1970-01-01
  • 2014-01-06
  • 2017-09-08
  • 1970-01-01
  • 2021-06-11
相关资源
最近更新 更多