当结构最终成为约束时该怎么办(由于接口 - 编译器变得脾气暴躁)?

What to do when a struct ends up becoming a constraint (due to interfaces - and the compiler goes grumpy)?

编辑:
我得出的结论是不可能满足我所有的要求!

原题...
我似乎 运行 陷入了不幸的困境。归结为这样一个事实,即结构不是有效的通用约束——这是完全理智的,但问题还是出现了……

为什么 曾经 想要将泛型限制为结构?
好吧,我 真的 不想这样做 - 这是界面设计的结果。

我将通过引导我到达那里的过程来解释我的困境:

假设我有一个接口与一些(可能)性能关键的通用方法,即:

interface ISomeInterface
{
    Result RelativeComputation(ISomeInterface other);
}

然而 ISomeInterface 本身并不是一个足够强大的约束:我希望使这个计算成为可能的细节成为一个实现细节——我特别想要 而不是 在接口本身中公开所需的数据,因为它 是该方法所需要的, 更糟糕的是,所需的数据是通用的(在该接口或任何相关接口中的其他任何地方都没有使用的类型),因此将其公开会在每个其他接口或使用 ISomeInterface 的 class 中乱扔额外的泛型。

所以解决上面的代码的下一个逻辑演变是:

interface ISomeInterface<T> where T : ISomeInterface<T>
{
    Result RelativeComputation(T other);
}

现在不需要公开实施细节,约束很强,在大多数情况下这将是圆满的结局。

但是,可能会出现 T 由另一个接口实现的情况,即:

interface IOtherInterface : ISomeInterface<IOtherInterface>
{
    //... *other stuff*
}

现在又出问题了! :(

第二个问题我真的没有 好的 解决方案,除了使 IOtherInterface 通用并在其上应用 Curiously Repeating Template Pattern 之外,并且然后再次添加计算方法。结果是:

interface IOtherInterface<T> : ISomeInterface<IOtherInterface<T>>
    where T : IOtherInterface<T>
{
    new Result RelativeComputation(T other);
    //... *other stuff*
}

...最终继承层次将达到具体类型,实现细节将保持隐藏。
诚然,这不是很优雅,但这也不是真正的问题,所以我离题了。

手头的真正问题是上面提到的"First"问题。
乍一看似乎很容易解决;使方法本身通用,如下所示:

interface ISomeInterface<T> where T : ISomeInterface<T>
{
    Result RelativeComputation<T2>(T2 other) where T2 : T;
}

现在参数可以是结构体了,不会再装箱了!
(即使上面的 T 是一个接口也是如此,例如 IOtherInterface。)

太棒了!
因此,让我们创建一个合适的结构,即实现 ISomeInterface(或更派生的接口,如 IOtherInterface),问题是,呃...哦。

如果您还没有弄明白,下面是该代码的样子:

struct MyPerformantStruct : ISomeInterface<MyPerformantStruct>
{
    Result RelativeComputation<T2>(T2 other) where T2 : MyPerformantStruct; // nope! :(
}

接口需要那个确切的签名,但是编译!

我希望有什么办法可以解决这个问题?
(允许没有装箱的结构 隐藏尽可能多的实现!)

澄清一下,我不希望有一种方法可以将结构用作通用约束,而是我希望有另一种解决方法,比如巧妙的设计改进(和/或 hack ).

编辑:

当前的答案和评论让我深入了解了一些相关的附加信息:

此代码说明需要约束到实际类型:

interface IExample
{
    bool IsLessThan<T>(T other) where T : IExample;
}

class ExampleClass : IExample
{
    short someValue;
    public bool IsLessThan<T>(T other) where T : IExample
    {
        return this.someValue < other.???; // <-- what now?
    }
}

首先,你应该问一个关于你正在解决的实际问题的问题。如果仅使用示例,很难理解您要做什么以及为什么要这样做。也许您处理问题的方式不对,或者是否有更好的解决方案来解决您的 实际 问题。有无数的设计模式可以解决许多问题。

您似乎在进行微优化。你测量过有问题的代码吗?你发现瓶颈真的存在了吗?

您想要如下所示的代码:

struct MyPerformantStruct : ISomeInterface<MyPerformantStruct>
{
    Result RelativeComputation<T2>(T2 other) where T2 : MyPerformantStruct;
}

但是……为什么?如果 MyPerformantStruct 是一个结构,那么,根据定义 ,没有任何 class 或结构可以从它继承。唯一可以满足 T2 的类型是... MyPerformantStruct。那么为什么不直接使用它呢?

interface ISomeInterface<T>
    where T : struct
{
    Result RelativeComputation(T other);
}

struct MyPerformantStruct : ISomeInterface<MyPerformantStruct>
{
    Result RelativeComputation(MyPerformantStruct other);
}

T 是类型上的泛型参数而不是成员上的泛型参数,这是有原因的吗?

interface ISomeInterface
{
    Result RelativeComputation<T>(T other)
        where T : struct;
}

struct MyPerformantStruct : ISomeInterface
{
    Result RelativeComputation<MyPerformantStruct>(MyPerformantStruct other);
}

你的代码不能处理结构之外的东西吗?对我来说似乎不太可能,所以你可以放弃 struct 约束。当 T 是一个结构时,您仍然可以获得未装箱结构的好处。

interface ISomeInterface
{
    Result RelativeComputation<T>(T other);
}

struct MyPerformantStruct : ISomeInterface
{
    Result RelativeComputation<MyPerformantStruct>(MyPerformantStruct other);
}

你确定你可以在不装箱的情况下处理这个结构吗?如果没有,您还不如对类型进行抽象并请求结构的接口,或者 object 如果您真的想要的话。我猜你使用结构的唯一原因是性能。这是错误的做法。结构应该表示不可变的 ,例如 date/time 对或百分比。结构不是出于性能原因使用的可变字段的无框集合。如果您尝试这样使用它们,您稍后会 运行 遇到问题。

您的问题有两个不同的问题:关于装箱的泛型类型定义中的结构约束,以及如何在程序集中隐藏实现细节。

正如您所指出的,您不能将泛型类型参数设置为显式 struct 类型。但是,您可以将泛型类型参数 T 强制为 struct 并实现特定接口:

interface ISomeInterface<T> where T : struct, ISomeInterface<T>
{
    Result RelativeComputation(T other);
}

现在关于装箱:泛型旨在防止装箱发生(请参阅 Benefits of Generics)。考虑以下程序:

public interface ISomeInterface
{
    int GetValue();
}

public struct SomeStruct : ISomeInterface
{
    public int GetValue() { return 42; }
}

public interface ISomeOtherInterface1<T> where T : ISomeInterface
{
    int ComputeStuff(T other);
}

public interface ISomeOtherInterface2<T> where T : struct, ISomeInterface
{
    int ComputeStuff(T other);
}

class ClassImplementingInterface1<T> : ISomeOtherInterface1<T> where T : ISomeInterface
{
    public int ComputeStuff(T other) { return other.GetValue(); }
}

class ClassImplementingInterface2<T> : ISomeOtherInterface2<T> where T : struct, ISomeInterface
{
    public int ComputeStuff(T other) { return other.GetValue(); }
}

class Program
{
    static void Main(string[] args)
    {
        var value = new SomeStruct();
        var obj1 = new ClassImplementingInterface1<SomeStruct>();
        var obj2 = new ClassImplementingInterface2<SomeStruct>();
        var obj3 = new ClassImplementingInterface1<ISomeInterface>();
        var result1 = obj1.ComputeStuff(value);
        var result2 = obj2.ComputeStuff(value);
        var result3 = obj3.ComputeStuff(value);
    }
}

在这个例子中,我有一个由结构实现的接口,以及两个不同的泛型接口:第二个将其泛型类型参数强制为一个结构,而第一个不是。在 Main 方法中,您会看到 3 种不同的使用方式 类。如果您查看 Visual Studio 中 Disassembly window 中最后 3 行代码产生的汇编代码,您会发现前两个调用的代码是相同的:没有装箱发生。这两个 ComputeStuff 方法的汇编代码也是相同的,因为代码和有效类型参数都是相同的。只有最后一个会导致装箱,因为它使用 ISomeInterface 类型作为有效类型参数,而接口类型在 CLR 世界中是引用类型,因此传递的任何值类型都会被装箱。


关于隐藏实现细节,我似乎不清楚你到底想向外部代码隐藏什么,但也许你缺少的是 public 类型可以实现内部接口,如果他们明确实施它们:

internal struct PrivateData { ... }

internal interface IMyInterface
{
    Result SomePrivateComputation(PrivateData data);
}

public class MyPublicClass : IMyInterface
{
    Result IMyInterface.SomePrivateComputation(PrivateData data)
    {
        ...
    }
}

然后您的程序集中的代码(以及任何 InternalsVisibleToAttribute 指定的朋友程序集)将能够执行以下操作:

MyPublicClass myObj;
...
((IMyInterface)myObj).SomePrivateComputation(myData); // No boxing here, no virtual call either: the compiler knows which method to invoke on MyPublicClass.

我得出结论,我喜欢做的事不能做。
必须付出一些东西,在这种情况下,它只是 IOtherInterface.
简而言之,如果一个人希望使用一个结构并且不想在 ISomeInterface 中遭受任何装箱 T 必须 是结构本身(该方法不再具有其自己的泛型)。

我能想到的任何解决方法都绝对比简单的装箱指令花费更多。

所以我将关闭它,因为它不可行。
感谢大家的回复。