具有基于静态成员的类型约束的泛型

时间:2017-02-14 22:53:11

标签: c# generics interface static generic-constraints

我试图将仿制药用于似乎不受支持的限制因素,并且我想知道是否有干净的解决方法。

我的初始尝试的问题取决于您不能在接口中拥有静态成员的事实,因此,您不能使用接口来约束基于静态成员的泛型声明中的类型。

考虑这个例子:

假设您正在使用通用上下文,但是您希望确保T的实例能够将自己序列化为流(可以是文件,字节列表),以及T可以从流中反序列化。

在编写的情况下,这很容易,因为可以假设您已经有一个实例可以使用(尽管您将无法序列化null):

public interface IStreamWritable
{
    // When called, this IStreamWritable instance
    // serializes itself to a stream.
    void Write(IStream stream);
}

...

void Write<T>(IStream stream, T t) where T : IStreamWritable
{
    t.Write(stream);
}

但是,在阅读的情况下,您遇到了一个问题:

public interface IStreamReadable
{
    // When called, returns an instance 
    // deserialized from the stream.
    void Read(IStream stream);
}

...

T Read<T>(IStream stream) where T : IStreamReadable, new()
{
    var t = new T();
    t.Read(stream);
    return t;
}

这看起来似乎有效,但它假设了如何实例化反序列化的对象。也许您想要返回现有实例而不是创建新实例?它还需要new()约束,这可能是不合需要的。

毕竟,当您在特定实例的上下文之外工作时,在静态上下文中工作是有意义的。所以你可以试试这个:

public interface IStreamReadable
{
    // When called, returns an instance 
    // deserialized from the stream.
    static IStreamReadable Read(IStream stream);
}

...

T Read(IStream stream) where T : IStreamReadable
{
    return T.Read(stream);
}

或者,或者,为了避免拳击:

public interface IStreamReadable<T> where T : IStreamReadable<T>
{
    // When called, returns an instance 
    // deserialized from the stream.
    static T Read(IStream stream);
}

...

T Read(IStream stream) where T : IStreamReadable<T>
{
    return T.Read(stream);
}

不幸的是,既不编译,因为你不能在接口中声明静态成员。但是,如果编译器允许我这样做,那么它将是理想的解决方案,因为它不会假设您如何处理实例化,而是将该责任推迟到接口实现者。

我找到了一个很好的解决方案,适用于结构体:

public interface IFoo
{
    void Foo();
}

...

CallFoo<T>() where T : struct, IFoo
{
    return default(T).Foo();
}

IFoo的实现者调用静态方法。当然,由于在这种情况下default(T)返回null,因此在引用类型的情况下,此方法将失败。 使用return new T().Foo();也可以使用,但这需要再次使用new()约束,并丢弃T不必要地创建垃圾的实例。

我考虑过以某种方式使用反射作为一种解决方法,但我想知道是否有人提出他们自己的工作来解决他们想要分享的这种限制。

2 个答案:

答案 0 :(得分:1)

如果您将创建IStreamReadable对象的责任委托给&#34; Factory&#34;类

,您的问题将得到解决
using System;

namespace ConsoleApplication5
{
    public interface IStream { }
    public interface IStreamWritable { void Write(IStream stream); }
    public interface IStreamReadable { void Read(IStream stream); }
    public interface IStreamReadableFactory { IStreamReadable Create(); }

    public class InstanceFactory : IStreamReadableFactory
    {
        public IStreamReadable Create() { throw new NotImplementedException(); }
    }
    public class StaticFactory : IStreamReadableFactory
    {
        public static IStreamReadable GetInstance() { throw new NotImplementedException(); }
        IStreamReadable IStreamReadableFactory.Create() { return GetInstance(); }
    }

    public static class Program
    {
        public static void Main()
        {
            IStream stream = null;
            var s1 = Read(new StaticFactory(), stream);
            var s2 = Read(new InstanceFactory(), stream);
        }

        static IStreamReadable Read(IStreamReadableFactory factory, IStream stream)
        {
            var t = factory.Create();
            t.Read(stream);
            return t;
        }
    }
}

答案 1 :(得分:1)

  

假设您正在使用通用上下文,但是您希望确保T的实例能够将自己序列化为流(可以是文件,字节列表),以及T可以从流中反序列化。

我个人不关心T的实例是否可以反序列化自己,因为它们可以序列化到流中。这样做的方法不是强制T为这些方法提供实现(因为该类可能还有其他职责),而是强迫某人提供可以的实现。

鉴于界面:

public interface IStreamDeserializer<T>
{
    T Read(IStream stream);
}

...你可以写一个像这样的方法:

public T GetFromFile<T>(string path, IStreamDeserializer<T> deserializer)
{
    using (var stream = GetFileStream(path))
    {
        return deserializer.Read(stream);
    }
}

因此,为了调用GetFromFile<Foo>(...),有人必须生成一个知道如何反序列化Foo个对象的类。他们依赖注入您的方法。

当然,反序列化器的存在可能不是 GetFromFile()的每个实现的先决条件 - 这是您的实现的一个方面,可能因您的方法而有所不同签名。所以你应该使用构造函数注入,这意味着你的类变得通用而不仅仅是你的方法。

public class FileEntityRetriever<T> : IFileEntityRetriever
{
    IStreamDeserializer<T> deserializer;

    public FileEntityRetriever(IStreamDeserializer<T> deserializer)
    {
        this.deserializer = deserializer;
    }

    public T GetFromFile(string path, IStreamDeserializer<T> deserializer)
    {
        using (var stream = GetFileStream(path))
        {
            return deserializer.Read(stream);
        }
    }

}