我有一个内存块的接口,应该由管理ram内存的类和管理磁盘上内存块的类来实现。第一类应支持引用类型,而第二类仅支持值类型。
关于接口示例,请考虑以下内容。 我没有为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>();
}
}
答案 0 :(得分:1)
您希望根据DiskMemoryBlock<T>
构造函数参数中的某些条件实例化RamMemoryBlock<T>
或Buffer<T>
:
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**)
{
buffer = (**conditions**) ? new DiskMemoryBlock<T>() : new RamMemoryBlock<T>();
}
}
不幸的是,在实例化类的对象(忽略反射jiggery-pokery)时,需要在编译时保证该类的任何类型约束。这意味着构建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>();
}
}
好的,所以我们有一些有用的东西,但是有一点问题 - 从构造函数中调用虚方法并不是一个好主意 - 派生类&#39;构造函数还没有运行,所以我们可以在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
的类型完全无关使用,只需给它一个函数,它将为它创建相关的类型。更好的是,将函数包装在Factory类中,以防我们以后需要更多选项:
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
的哪个版本#39;在编译时重新实例化。
从好的方面来说,该决定与Buffer
之间的所有类只需要知道IMemoryBlockFactory
的存在。这意味着你可以改变一切并保持涟漪效应相当小:
IMemoryBlock
,则可以创建额外的IMemoryBlockFactory
课程并完成。 IMemoryBlock
类型,则可以更改CreateMemoryBlock
以仅针对工厂和受Buffer
实施影响的不同参数。如果您不需要这些优势(内存块不太可能改变,并且您倾向于使用具体类型实例化Buffer
个对象),那么无论如何,请不要打扰拥有工厂的额外复杂性,并在此答案的大约一半处使用该版本(其中Func<IMemoryBlock>
被传递到构造函数中)。
答案 1 :(得分:0)
只要您希望DiskMemoryBlock
仅包含值类型,就可以直接限制它:
class DiskMemoryBlock<T> : IMemoryBlock<T> where T : struct
这是泛型编程中的一种常见模式:您可以使用无约束类型参数定义通用接口,当您下降类层次结构时,可以通过在约束上定义子类来进一步细化这些参数。
当你创建一个DiskMemoryBlock
时,类型检查器将尝试解决约束。
new DiskMemoryBlock<int>(); // ok
new DiskMemoryBlock<string>(); // type error
如果您希望DiskMemoryBlock
能够包含引用类型,那么您就不能使用GenericBitConverter.ToValue<T>
。