【问题标题】:How to limit a generic class to certain types (including native data types)如何将泛型类限制为某些类型(包括本机数据类型)
【发布时间】:2014-08-20 03:19:59
【问题描述】:

我想创建一个通用结构(基本上是一个数组)并将可能的类型限制为 ISerializable 类型和一堆本机数据类型,如 int、uint、float、double、char 等。问题是我无法标记这些具有接口的本机数据类型和我的研究表明,在泛型类型约束构造(where 子句)中不可能使用 or 关键字之类的东西。所以问题是我如何才能意识到这一点?

如果您对上下文感兴趣:我有一个 BinaryStream 类负责读取/写入流。 custom ISerializable 接口具有函数void Serialize(BinaryStream f),可以从/向流f 读取或写入(取决于f 中的状态)。实际写入或读取的当然是构成结构的本机数据类型。这些是通过f.Transfer(ref data) 读取或写入的。使用 .NET 框架中的标准 BinarySerializer 不是一种选择,因为它必须以自定义方式完成。

public class AutoArray<T> : ISerializable where T : ISerializable //or int or uint or float etc.
{
    private uint n;
    private T[] data;

    public void Serialize(BinaryStream f)
    {
        f.Transfer(ref n);
        for (int i = 0; i < n; i++)
            if (data[i] is ISerializable) data[i].Serialize(f);
            else f.Transfer(ref data[i]);
    }
}

【问题讨论】:

    标签: c# generics


    【解决方案1】:

    您的研究为您提供了正确的信息 - .NET 不允许您将泛型限制为某些抽象数字数据类型,因为它们(数字数据类型)没有任何通用接口。这是一种耻辱,有时也是一个问题,但事实仍然如此。

    如果您确实有非常通用的代码可以处理数字类型,那么您可以尝试在 C++\CLI 中实现您的类 - 它是 C++,因此它允许您使用模板,并且它是支持泛型的 .NET 语言:

    // C# - use base interface for your serializer, define it in separate assembly(or directly in C++\CLI)
    public interface IMySerializer<T> {...}
    
    // C++\CLI - add the reference to the project with IMySerializer<T>
    template<class Type>
    public ref abstract class MyNumericSerializerBase : IMySerializer<Type> {...};
    
    // C++\CLI - you can't use template class in C# - it must be specialized
    public ref class MyIntSerializer : MyNumericSerializerBase<Int32> {...};
    

    然后添加创建的 C++\CLI dll 作为对项目的引用。好吧,您仍然必须为每个数字类型创建这样的MyIntSerializer,但至少您不必复制所有代码。为了有效地获得所需的IMySerializer&lt;T&gt;,您可以使用一些工厂,通过属性或IsAssignableFrom 的反射来搜索特定的IMySerializer&lt;T&gt;

    // C#
    public class MySerializerFactory
    {
        public IMySerializer<T> GetSerializer<T>() {...}
    }
    

    这不是最简单的解决方案,但当我需要创建仅在计算中数字类型不同的类型时,我会使用它。

    编辑:

    使用 C++\CLI 会不会有点过头了?好吧,对于简单的情况,应用起来并不难,但如果你不会使用类似的东西

    template<class Type>
    public ref abstract class MyNumericSerializerBase : IMySerializer<Type> 
    { 
    public:
        Type  GetValue(Type first, Type second)
        {
            return first + (second / 2); // The REAL advantage(and potential problems source) of C++\CLI templates
        }
     }
    

    那么除了避免代码重复之外,这种方法并没有真正的优势。而且,考虑到数字类型很少,您可以在 C# 中实现特定的序列化程序:

    // C#
    public class MyInt32Serializer: IMySerializer<Int32> {...}
    

    并使用相同的工厂(或定位器)模式:

    // C#
    public class MySerializerFactory
    {
        public IMySerializer<T> GetSerializer<T>() {...}
    }
    

    具体的实现者可以像here中描述的那样找到

    【讨论】:

    • 嗯,我想避免创建一个新的 dll,因为这样一个小功能。使用另一种语言似乎也很“混乱”。我已经在使用不安全的功能了,如果这有帮助的话^^
    • 封闭的通用/工厂模式是 C# 鼓励你在这里做的。
    【解决方案2】:

    这是一个非常混乱的实现,但是如果让泛型类型不受约束并在构造函数中检查它,你会怎么想?如果类型 T 不在您的允许类型白名单上,基本上让它抛出 NotImplementedException。

    【讨论】:

    • 是的,我也考虑过,这是一个可能的解决方案,缺点是会丢失编译时错误检测。你说它超级乱。我也愿意接受更好的实施。所以,如果你或有人能启发我:)
    • 对不起,澄清:我的意思是我提出的解决方案很混乱。我并不是要质疑您的解决方案/尝试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多