有没有办法在定义它的类中引入约束到泛型类型

本文关键字:约束 泛型类型 定义 有没有 | 更新日期: 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>的唯一方法是:

  1. 直接将T指定为值类型(例如 var block = new DiskMemoryBlock<int>()
  2. 在具有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>())
    {
    }
}

这看起来更好 - 没有虚拟通话,一切都很好。虽然。。。

我们遇到的问题是试图在运行时在DiskMemoryBlockRamMemoryBlock之间进行选择。我们已经解决了这个问题,但是现在如果你想要一个 Buffer ,你必须在BufferValueBuffer之间进行选择。没关系 - 我们可以一直做同样的把戏,对吧?

好吧,我们可以,但这会为每个类创建两个版本。这是很多工作和痛苦。如果存在第三个约束 - 仅处理引用类型的缓冲区,还是特殊的节省空间的缓冲区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> .