当结构最终成为约束时该怎么办(由于接口 - 编译器变得脾气暴躁)?
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*
}
现在又出问题了! :(
- 首先,如开头所述,此方法调用可能会在性能关键代码中结束,因此在这些情况下,可能需要将
other
参数设为结构。然而,使用上面的代码,这将不可避免地被装箱破坏。
- 其次,
RelativeComputation(T other)
中对 T
的约束看似很强,实际上太弱了——除非我们选择再次公开实现细节! (如前所述直接在 ISomeInterface
中,或在 IOtherInterface
中。)
第二个问题我真的没有 好的 解决方案,除了使 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 ).
编辑:
当前的答案和评论让我深入了解了一些相关的附加信息:
- 接口和实现类型可以驻留在不同的程序集中。 (界面在库中。)
- 我不是说它必须是一个结构,我是说它应该可以使用class 或一个结构(没有装箱)。
- 我指的"implementation details"是一些值类型(可能不止一个)。有问题的方法是一种
bool IsLessThan(...)
类型的方法,如果没有装箱、拆箱和 运行 时间检查我似乎无法评估(不,谢谢!)- 或 不知道底层类型的实现细节并将其暴露在接口中,从而锁定实现并破坏整个抽象点 - 或 将其限制在接口级别,这还有上面提到的其他问题 - or 也在方法级别限制它,这是我上面想做的,但事实证明明确阻止 T
成为一个结构。
- (我更喜欢留在 .net 3.5 上,但这并不重要。)
- 我的问题描述很长,为什么接口和方法都需要泛型并不是很明显,所以我将提供一个代码示例并说明为什么它不能编译(或者需要 运行时间施法):
此代码说明需要约束到实际类型:
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
必须 是结构本身(该方法不再具有其自己的泛型)。
我能想到的任何解决方法都绝对比简单的装箱指令花费更多。
所以我将关闭它,因为它不可行。
感谢大家的回复。
编辑:
我得出的结论是不可能满足我所有的要求!
原题...
我似乎 运行 陷入了不幸的困境。归结为这样一个事实,即结构不是有效的通用约束——这是完全理智的,但问题还是出现了……
为什么 曾经 想要将泛型限制为结构?
好吧,我 真的 不想这样做 - 这是界面设计的结果。
我将通过引导我到达那里的过程来解释我的困境:
假设我有一个接口与一些(可能)性能关键的通用方法,即:
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*
}
现在又出问题了! :(
- 首先,如开头所述,此方法调用可能会在性能关键代码中结束,因此在这些情况下,可能需要将
other
参数设为结构。然而,使用上面的代码,这将不可避免地被装箱破坏。 - 其次,
RelativeComputation(T other)
中对T
的约束看似很强,实际上太弱了——除非我们选择再次公开实现细节! (如前所述直接在ISomeInterface
中,或在IOtherInterface
中。)
第二个问题我真的没有 好的 解决方案,除了使 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 ).
编辑:
当前的答案和评论让我深入了解了一些相关的附加信息:
- 接口和实现类型可以驻留在不同的程序集中。 (界面在库中。)
- 我不是说它必须是一个结构,我是说它应该可以使用class 或一个结构(没有装箱)。
- 我指的"implementation details"是一些值类型(可能不止一个)。有问题的方法是一种
bool IsLessThan(...)
类型的方法,如果没有装箱、拆箱和 运行 时间检查我似乎无法评估(不,谢谢!)- 或 不知道底层类型的实现细节并将其暴露在接口中,从而锁定实现并破坏整个抽象点 - 或 将其限制在接口级别,这还有上面提到的其他问题 - or 也在方法级别限制它,这是我上面想做的,但事实证明明确阻止T
成为一个结构。 - (我更喜欢留在 .net 3.5 上,但这并不重要。)
- 我的问题描述很长,为什么接口和方法都需要泛型并不是很明显,所以我将提供一个代码示例并说明为什么它不能编译(或者需要 运行时间施法):
此代码说明需要约束到实际类型:
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
必须 是结构本身(该方法不再具有其自己的泛型)。
我能想到的任何解决方法都绝对比简单的装箱指令花费更多。
所以我将关闭它,因为它不可行。
感谢大家的回复。