有没有办法在定义它的类中引入约束到泛型类型
本文关键字:约束 泛型类型 定义 有没有 | 更新日期: 2023-09-27 18:31:08
我有一个内存块的接口,应该由管理内存内存的类和管理磁盘上内存块的类来实现。第一个类还应支持引用类型,而第二个类仅支持值类型。
对于接口示例,请考虑以下内容。我没有为 T
设置约束以允许支持第一个类的引用类型:
public interface IMemoryBlock<T>
{
T this[int index] {get; }
}
第二个类在初始化时检查 T 是否为值类型 (typeof(T).IsValueType
),并且应该如下所示:
public class DiskMemoryBlock<T> : IMemoryBlock<T>
{
public T this[int index]
{
get
{
byte[] bytes = ReadBytes(index * sizeOfT, sizeOfT);
return GenericBitConverter.ToValue<T>(bytes, 0);
}
}
}
这不起作用,因为GenericBitConverter.ToValue<T>
需要对 T
的值类型约束。有没有办法在以后引入约束来解决这种情况?否则,在这种情况下,除了编写没有约束的自定义GenericBitConverter.ToValue<T>
之外,您有什么建议?
编辑
我忘了指定,然后我有一个Buffer<T>
类,根据参数应该初始化DiskMemoryBlock<T>
或RamMemoryBlock<T>
:
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**)
{
buffer = (**conditions**) ? new DiskMemoryBlock<T>() : new RamMemoryBlock<T>();
}
}
@Benjamin霍奇森已经以与我相同的方式回答了您问题的主要部分,所以我不会浪费空间重复他。这只是对您的编辑的回应。
您希望根据来自Buffer<T>
构造函数参数的某些条件实例化DiskMemoryBlock<T>
或RamMemoryBlock<T>
:
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**)
{
buffer = (**conditions**) ? new DiskMemoryBlock<T>() : new RamMemoryBlock<T>();
}
}
不幸的是,当实例化类的对象(忽略反射抖动扑克)时,需要在编译时保证该类的任何类型约束。这意味着构建DiskMemoryBlock<T>
的唯一方法是:
- 直接将
T
指定为值类型(例如var block = new DiskMemoryBlock<int>()
) - 在具有
struct
约束的上下文中构建它。
你的情况不是这些。鉴于拥有通用Buffer
似乎很重要,选项 1 没有任何帮助,因此选项 2 是您在这里唯一的选择。
让我们看看是否可以解决此问题。
我们可以尝试将困难的IMemoryBlock
创建放入虚拟方法中,并创建一个仅值类型的 Buffer
子类,它可以毫无问题地创建一个DiskMemoryBlock
:
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**)
{
buffer = (**conditions**)
? CreateMemoryBlockPreferDisk()
: new RamMemoryBlock<T>();
}
protected virtual IMemoryBlock<T> CreateMemoryBlockPreferDisk()
{
return new RamMemoryBlock<T>();
}
}
public class ValueBuffer<T> : Buffer<T> where T : struct
{
public ValueBuffer(**params**) : base(**params**) { }
protected override IMemoryBlock<T> CreateMemoryBlockPreferDisk()
{
return new DiskMemoryBlock<T>();
}
}
好的,所以我们有一些工作的东西,但有一个问题 - 从构造函数调用虚拟方法不是一个好主意 - 派生类的构造函数尚未运行,因此我们可以在ValueBuffer
中进行各种未初始化的事情。在这种情况下没关系,因为重写不使用派生类的任何成员(实际上没有任何成员),但是如果有更多的子类,它为将来意外中断的事情敞开了大门。
函数传递给基类,而不是让基类调用派生类?
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**)
: this(**params**, () => new RamMemoryBlock<T>())
{
}
protected Buffer(**params**, Func<IMemoryBlock<T>> createMemoryBlockPreferDisk)
{
buffer = (**conditions**)
? createMemoryBlockPreferDisk()
: new RamMemoryBlock<T>();
}
}
public class ValueBuffer<T> : Buffer<T>
where T : struct
{
public ValueBuffer(**params**)
: base(**params**, () => new DiskMemoryBlock<T>())
{
}
}
这看起来更好 - 没有虚拟通话,一切都很好。虽然。。。
我们遇到的问题是试图在运行时在DiskMemoryBlock
和RamMemoryBlock
之间进行选择。我们已经解决了这个问题,但是现在如果你想要一个 Buffer
,你必须在Buffer
和ValueBuffer
之间进行选择。没关系 - 我们可以一直做同样的把戏,对吧?
好吧,我们可以,但这会为每个类创建两个版本。这是很多工作和痛苦。如果存在第三个约束 - 仅处理引用类型的缓冲区,还是特殊的节省空间的缓冲区bool
缓冲区,该怎么办?
该解决方案类似于将createMemoryBlockPreferDisk
传递到 Buffer
构造函数的方法 - 使Buffer
与它正在使用的IMemoryBlock
类型完全无关,只需给它一个函数,该函数将为它创建相关类型。更好的是,将函数包装在工厂类中,以防我们以后需要更多选项:
public enum MemoryBlockCreationLocation
{
Disk,
Ram
}
public interface IMemoryBlockFactory<T>
{
IMemoryBlock<T> CreateMemoryBlock(MemoryBlockCreationLocation preferredLocation);
}
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**, IMemoryBlockFactory<T> memoryBlockFactory)
{
var preferredLocation = (**conditions**)
? MemoryBlockCreationLocation.Disk
: MemoryBlockCreationLocation.Ram;
buffer = memoryBlockFactory.CreateMemoryBlock(preferredLocation);
}
}
public class GeneralMemoryBlockFactory<T> : IMemoryBlockFactory<T>
{
public IMemoryBlock<T> CreateMemoryBlock(MemoryBlockCreationLocation preferredLocation)
{
// We can't create a DiskMemoryBlock in general - ignore the preferred location and return a RamMemoryBlock.
return new RamMemoryBlock<T>();
}
}
public class ValueTypeMemoryBlockFactory<T> : IMemoryBlockFactory<T>
where T : struct
{
public IMemoryBlock<T> CreateMemoryBlock(MemoryBlockCreationLocation preferredLocation)
{
switch (preferredLocation)
{
case MemoryBlockCreationLocation.Ram:
return new RamMemoryBlock<T>();
case MemoryBlockCreationLocation.Disk:
default:
return new DiskMemoryBlock<T>();
}
}
}
我们仍然需要在某个地方决定我们需要哪个版本的IMemoryBlockFactory
,但如上所述,没有办法解决这个问题 - 类型系统需要知道你在编译时实例化哪个版本的IMemoryBlock
。
从好的方面来说,该决定和Buffer
之间的所有类只需要知道IMemoryBlockFactory
的存在。这意味着您可以改变事物并保持相当小的连锁反应:
- 如果您需要额外类型的
IMemoryBlock
,请创建一个额外的IMemoryBlockFactory
类,然后就完成了。 - 如果需要根据更复杂的因素确定
IMemoryBlock
类型,则可以更改CreateMemoryBlock
以采用不同的参数,仅Buffer
工厂和实现受到影响。
如果你不需要这些优点(内存块不太可能改变,你倾向于用具体类型实例化Buffer
对象),那么无论如何,不要为拥有工厂的额外复杂性而烦恼,并使用大约一半的版本这个答案(Func<IMemoryBlock>
被传递到构造函数中)。
只要你打算让DiskMemoryBlock
只包含值类型,你就可以直接约束它:
class DiskMemoryBlock<T> : IMemoryBlock<T> where T : struct
这是泛型编程中的常见模式:使用不受约束的类型参数定义通用接口,随着类层次结构的下降,子类在约束上附加,这些参数会变得更加精细。
创建DiskMemoryBlock
时,类型检查器将尝试求解约束。
new DiskMemoryBlock<int>(); // ok
new DiskMemoryBlock<string>(); // type error
如果您希望DiskMemoryBlock
能够包含引用类型,那么,您不能使用 GenericBitConverter.ToValue<T>
.