我有一个通用类,涉及可以从字符串反序列化的小部件。通用类的实例将这些小部件之一的类型作为模板参数,然后从字符串创建这些小部件。我希望使用C#泛型的协方差属性编写类似WidgetUser<IWidget>的代码来处理可能是WidgetUser<RedWidget>WidgetUser<BlueWidget>的对象。问题是要从WidgetUser<T>内的字符串创建窗口小部件,我不得不添加new()作为保护。这使WidgetUser<IWidget>成为无效类型。目前,我有这样的代码:

interface IWidget
{
    // Makes this widget into a copy of the serializedWidget
    void Deserialize(string serializedWidget);
}
class WidgetUser<T> where T : IWidget, new()
{
    public void MakeAndUse(string serializedWidget)
    {
        var widget = new T();
        widget.Deserialize(serializedWidget);
        Use(widget);
    }
}


使用此代码,我可以使WidgetUser<BlueWidget>很好,因为BigWidget满足new()。我无法编写WidgetUser<IWidget>,因为不能保证IWidget(或等效的抽象类)的实例可以与new()一起使用。解决方法可能是这样的:

abstract class WidgetUser
{
    public abstract void MakeAndUse();
}

class WidgetUser<T> : WidgetUser
    where T : IWidget, new()
{
    /* same as before but with an 'override' on MakeAndUse */
}


使用此代码,我可以创建一个WidgetUser<BlueWidget>,然后编写仅处理WidgetUser的代码。我可以用类似的代码使用抽象类BaseWidget而不是IWidget来完成几乎相同的事情。这是功能性的,但是我怀疑还有一种更直接的方法不会强迫我定义一个虚拟类。如何在不创建伪类或额外工厂的情况下将意图传达给类型系统。我只想要一个说“您可以从字符串中制作一个”的界面。

TL; DR:
有什么方法可以编写接口或抽象类,使我可以从字符串创建实例,但不需要我以new()作为WidgetUser<T>的保护对象?

最佳答案

这里的问题是您的Deserialize()方法应该是静态方法。因此,它不应该是IWidget本身的成员-它应该是factory接口的成员,或者应该是具体Widget类的静态成员,而该Widget类是从具体factory方法调用的。我在下面显示后一种方法。

(或者,您可以使用Func<IWidget>委托来指定它,但是通常提供完整的工厂接口。)

因此,我建议您创建工厂界面:

interface IWidgetFactory
{
    IWidget Create(string serialisedWidget);
}


然后从Deserialize()中删除​​IWidget

interface IWidget
{
    // .. Whatever
}


然后向Deserialize()的每个具体实现中添加一个静态IWidget方法:

class MyWidget: IWidget
{
    public static MyWidget Deserialize(string serializedWidget)
    {
        // .. Whatever you need to deserialise into myDeserializedObject
        return myDeserializedObject;
    }

    // ... Any needed IWidget-implementing methods and properties.
}


然后使用具体小部件类中的静态Deserialize()方法为具体小部件类实现工厂:

sealed class MyWidgetFactory : IWidgetFactory
{
    public IWidget Create(string serialisedWidget)
    {
        return MyWidget.Deserialize(serialisedWidget);
    }
}


然后向您的WidgetUser类添加一个接受IWidgetFactory的构造函数,并在MakeAndUse()中使用它:

class WidgetUser
{
    public WidgetUser(IWidgetFactory widgetFactory)
    {
        this.widgetFactory = widgetFactory;
    }

    public void MakeAndUse(string serializedWidget)
    {
        var widget = widgetFactory.Create(serializedWidget);
        Use(widget);
    }

    private readonly IWidgetFactory widgetFactory;
}


请注意,在这种情况下,您不再需要WidgetUser的type参数,因此已将其删除。

然后,在创建WidgetUser时,必须提供工厂:

var widgetUser = new WidgetUser(new MyWidgetFactory());

...

widgetUser.MakeAndUse("MySerializedWidget1");
widgetUser.MakeAndUse("MySerializedWidget2");


通过工厂允许更多的灵活性。

例如,假设您的序列化方案包括一种从序列化的字符串中分辨出它是哪种小部件的方法。为了简单起见,假设它是"[MyWidget]"开头为MyWidget,如果是["MyOtherWidget"]开头则是MyOtherWidget

然后,您可以实现一个充当“虚拟构造函数”的工厂,该工厂可以在给定序列化字符串的情况下创建任何类型的小部件,如下所示:

sealed class GeneralWidgetFactory: IWidgetFactory
{
    public IWidget Create(string serialisedWidget)
    {
        if (serialisedWidget.StartsWith("[MyWidget]"))
            return myWidgetFactory.Create(serialisedWidget);
        else if (serialisedWidget.StartsWith("[MyOtherWidget]"))
            return myOtherWidgetFactory.Create(serialisedWidget);
        else
            throw new InvalidOperationException("Don't know how to deserialize a widget from: " + serialisedWidget);

    }

    readonly MyWidgetFactory      myWidgetFactory      = new MyWidgetFactory();
    readonly MyOtherWidgetFactory myOtherWidgetFactory = new MyOtherWidgetFactory();
}


请注意,这通常不是最好的处理方法-最好使用诸如Autofac之类的依赖容器来管理此类操作。

08-28 18:47