有没有办法在定义它的 class 中引入对泛型类型的约束?
Is there a way to introduce a constraint to a generic type inside the class defining it?
我有一个内存块的接口,应该通过 class 管理 ram 内存和 class 管理磁盘上的内存块来实现。第一个 class 应该也支持引用类型,而第二个只支持值类型。
至于接口示例,请考虑以下内容。 我没有为 T
设置约束以允许支持第一个 class:
的引用类型
public interface IMemoryBlock<T>
{
T this[int index] {get; }
}
第二个 class 在初始化时检查 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>
class,根据参数应该初始化 DiskMemoryBlock<T>
或 RamMemoryBlock<T>
:
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**)
{
buffer = (**conditions**) ? new DiskMemoryBlock<T>() : new RamMemoryBlock<T>();
}
}
只要你想让一个DiskMemoryBlock
只包含值类型,你可以直接约束它:
class DiskMemoryBlock<T> : IMemoryBlock<T> where T : struct
这是泛型编程中的一种常见模式:您使用不受约束的类型参数定义通用接口,当您下降 class 层次结构时,这些接口会随着 subclasses 附加约束而变得更加细化。
当您创建一个DiskMemoryBlock
时,类型检查器将尝试解决约束。
new DiskMemoryBlock<int>(); // ok
new DiskMemoryBlock<string>(); // type error
如果您希望 DiskMemoryBlock
能够包含引用类型,那么,您不能使用 GenericBitConverter.ToValue<T>
。
@Benjamin Hodgson 你问题的主要部分和我一样,所以我不会浪费 space 重复他。这只是对您的编辑的回应。
您想根据来自 Buffer<T>
构造函数参数的某些条件实例化 DiskMemoryBlock<T>
或 RamMemoryBlock<T>
:
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**)
{
buffer = (**conditions**) ? new DiskMemoryBlock<T>() : new RamMemoryBlock<T>();
}
}
不幸的是,在实例化 class 的对象时(忽略反射 jiggery-pokery),class 上的任何类型约束都需要在编译时得到保证。这意味着您可以构造 DiskMemoryBlock<T>
的唯一方法是:
- 直接将
T
指定为值类型(例如var block = new
DiskMemoryBlock<int>()
)
- 在具有
struct
约束的上下文中构造它。
你的情况不是这两种情况。鉴于拥有通用 Buffer
似乎很重要,选项 1 没有任何帮助,因此选项 2 是您唯一的选择。
让我们看看是否可以解决这个问题。
我们可以尝试把困难的IMemoryBlock
创建放到一个虚方法中,创建一个Buffer
的value-type-only子class,它可以创建一个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>();
}
}
好的,所以我们有一些可行的东西,但是有一点问题 - 从构造函数调用虚拟方法不是一个好主意 - 派生的 class' 构造函数没有 运行 然而,所以我们可以在 ValueBuffer
中进行各种未初始化的事情。在这种情况下没关系,因为覆盖不使用派生 class 的任何成员(事实上,没有任何成员),但如果有的话,它会为将来意外破坏打开大门还有没有子classes.
所以也许不是让基 class 调用派生 class,我们可以让派生 class 将函数传递给基 class?
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
之间做出选择。没关系 - 我们可以一直这样做,对吧?
好吧,我们可以,但那是为每个 class 一直创建两个版本。这是很多工作和痛苦。如果还有第三个约束 - 一个只处理引用类型的缓冲区,或者一个特殊的 space 高效 bool
缓冲区怎么办?
解决方案类似于将 createMemoryBlockPreferDisk
传递给 Buffer
的构造函数的方法 - 让 Buffer
完全不知道它正在使用的 IMemoryBlock
的类型,并给它一个函数,该函数将为它创建相关类型。更好的是,将函数包装在工厂 class 中,以防我们以后需要更多选项:
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
之间的所有 class 只需要知道 IMemoryBlockFactory
的存在。这意味着您可以改变事物并保持涟漪效应相当小:
- 如果您需要额外的
IMemoryBlock
类型,您可以创建一个额外的 IMemoryBlockFactory
class 就可以了。
- 如果您需要根据更复杂的因素来决定
IMemoryBlock
类型,您可以更改 CreateMemoryBlock
以采用不同的参数,只有工厂和 Buffer
实施受到影响。
如果你不需要这些优势(内存块不太可能改变并且你倾向于实例化 Buffer
具有具体类型的对象),那么无论如何,不要为额外的而烦恼拥有工厂的复杂性,并使用这个答案中途的版本(Func<IMemoryBlock>
被传递到构造函数中)。
我有一个内存块的接口,应该通过 class 管理 ram 内存和 class 管理磁盘上的内存块来实现。第一个 class 应该也支持引用类型,而第二个只支持值类型。
至于接口示例,请考虑以下内容。 我没有为 T
设置约束以允许支持第一个 class:
public interface IMemoryBlock<T>
{
T this[int index] {get; }
}
第二个 class 在初始化时检查 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>
class,根据参数应该初始化 DiskMemoryBlock<T>
或 RamMemoryBlock<T>
:
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**)
{
buffer = (**conditions**) ? new DiskMemoryBlock<T>() : new RamMemoryBlock<T>();
}
}
只要你想让一个DiskMemoryBlock
只包含值类型,你可以直接约束它:
class DiskMemoryBlock<T> : IMemoryBlock<T> where T : struct
这是泛型编程中的一种常见模式:您使用不受约束的类型参数定义通用接口,当您下降 class 层次结构时,这些接口会随着 subclasses 附加约束而变得更加细化。
当您创建一个DiskMemoryBlock
时,类型检查器将尝试解决约束。
new DiskMemoryBlock<int>(); // ok
new DiskMemoryBlock<string>(); // type error
如果您希望 DiskMemoryBlock
能够包含引用类型,那么,您不能使用 GenericBitConverter.ToValue<T>
。
@Benjamin Hodgson
您想根据来自 Buffer<T>
构造函数参数的某些条件实例化 DiskMemoryBlock<T>
或 RamMemoryBlock<T>
:
public class Buffer<T>
{
IMemoryBlock<T> buffer;
public Buffer(**params**)
{
buffer = (**conditions**) ? new DiskMemoryBlock<T>() : new RamMemoryBlock<T>();
}
}
不幸的是,在实例化 class 的对象时(忽略反射 jiggery-pokery),class 上的任何类型约束都需要在编译时得到保证。这意味着您可以构造 DiskMemoryBlock<T>
的唯一方法是:
- 直接将
T
指定为值类型(例如var block = new DiskMemoryBlock<int>()
) - 在具有
struct
约束的上下文中构造它。
你的情况不是这两种情况。鉴于拥有通用 Buffer
似乎很重要,选项 1 没有任何帮助,因此选项 2 是您唯一的选择。
让我们看看是否可以解决这个问题。
我们可以尝试把困难的IMemoryBlock
创建放到一个虚方法中,创建一个Buffer
的value-type-only子class,它可以创建一个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>();
}
}
好的,所以我们有一些可行的东西,但是有一点问题 - 从构造函数调用虚拟方法不是一个好主意 - 派生的 class' 构造函数没有 运行 然而,所以我们可以在 ValueBuffer
中进行各种未初始化的事情。在这种情况下没关系,因为覆盖不使用派生 class 的任何成员(事实上,没有任何成员),但如果有的话,它会为将来意外破坏打开大门还有没有子classes.
所以也许不是让基 class 调用派生 class,我们可以让派生 class 将函数传递给基 class?
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
之间做出选择。没关系 - 我们可以一直这样做,对吧?
好吧,我们可以,但那是为每个 class 一直创建两个版本。这是很多工作和痛苦。如果还有第三个约束 - 一个只处理引用类型的缓冲区,或者一个特殊的 space 高效 bool
缓冲区怎么办?
解决方案类似于将 createMemoryBlockPreferDisk
传递给 Buffer
的构造函数的方法 - 让 Buffer
完全不知道它正在使用的 IMemoryBlock
的类型,并给它一个函数,该函数将为它创建相关类型。更好的是,将函数包装在工厂 class 中,以防我们以后需要更多选项:
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
之间的所有 class 只需要知道 IMemoryBlockFactory
的存在。这意味着您可以改变事物并保持涟漪效应相当小:
- 如果您需要额外的
IMemoryBlock
类型,您可以创建一个额外的IMemoryBlockFactory
class 就可以了。 - 如果您需要根据更复杂的因素来决定
IMemoryBlock
类型,您可以更改CreateMemoryBlock
以采用不同的参数,只有工厂和Buffer
实施受到影响。
如果你不需要这些优势(内存块不太可能改变并且你倾向于实例化 Buffer
具有具体类型的对象),那么无论如何,不要为额外的而烦恼拥有工厂的复杂性,并使用这个答案中途的版本(Func<IMemoryBlock>
被传递到构造函数中)。