2016-01-20 70 views
0

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

为什么不能在'DiskMemoryBlock'中为'T'增加'struct'约束? – Lee

+0

你想'DiskMemoryBlock '被混合,还是只有值类型? – Eris

+0

您可以使用反射来做到这一点,并在运行时构建'GenericBitConverter.ToValue'方法。 – Eris

回答

1

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

要实例或者基于某些条件来自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之间进行选择。不管 - 我们可以一直做同样的技巧,对吧?

那么,我们可以,但是这是每个班级创建两个版本。这是很多工作和痛苦。那么如果还有第三个约束 - 一个只处理参考类型的缓冲区,或者一个特殊的高效缓冲区?

的解决方案类似于通过createMemoryBlockPreferDiskBuffer构造函数的方法 - 让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>被传入构造函数)。

+0

感谢Philip!你的解决方案有效我选择了与工厂的一个,它对我的​​情况非常有效。 Kaveh Hadjari在我的帖子的评论中提出,在缓冲区的构造函数之外有'IMemoryBuffer ',但是这不起作用,因为缓冲区可以在可扩展模式下工作,并且他需要在创建内存时“独立”块。也感谢您花时间解释所有步骤! –

0

只要你打算一个DiskMemoryBlock只包含值类型,您可以直接限制它:

class DiskMemoryBlock<T> : IMemoryBlock<T> where T : struct 

这是泛型编程中的一种常见模式:您可以使用无约束的类型参数定义常规接口,当您下降类层次结构时,可以使用子类在约束上进行更加细化。

当你创建 a DiskMemoryBlock,类型检查器将尝试解决约束。

new DiskMemoryBlock<int>(); // ok 
new DiskMemoryBlock<string>(); // type error 

如果你想有一个DiskMemoryBlock能够包含引用类型的话,那么,你不能使用GenericBitConverter.ToValue<T>

+0

请看看我的编辑,看看为什么我仍然有问题,即使您的解决方案 –

相关问题