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

时间:2016-01-20 18:08:40

标签: c# generics type-constraints

我有一个内存块的接口,应该由管理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>();
    }
}

2 个答案:

答案 0 :(得分:1)

@Benjamin Hodgson has answered你问题的主要部分与我的方式相同,所以我不会浪费空间重复他。这只是对您的修改的回复。

您希望根据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>的唯一方法是:

  1. 直接指定T作为值类型(例如var block = new DiskMemoryBlock<int>()
  2. 在具有struct约束的上下文中构造它。
  3. 你的情况既不是这些。鉴于通用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>())
        {
        }
    }
    

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

    我们遇到的问题是在运行时尝试在DiskMemoryBlockRamMemoryBlock之间进行选择。我们已修复此问题,但现在如果您需要Buffer,则必须在BufferValueBuffer之间进行选择。无论如何 - 我们可以一直做同样的伎俩,对吗?

    嗯,我们可以,但是这样就创造了每个班级的两个版本。那是很多工作和痛苦。如果有第三个约束 - 一个仅处理引用类型的缓冲区,或一个特殊的空间有效bool缓冲区会怎样?

    解决方案类似于将createMemoryBlockPreferDisk传递到Buffer的构造函数的方法 - 使BufferIMemoryBlock的类型完全无关使用,只需给它一个函数,它将为它创建相关的类型。更好的是,将函数包装在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>