为派生类的类型静态定义选项

时间:2019-02-14 02:40:53

标签: c# .net attributes static-methods

我正在开发一个框架,其中从框架的抽象类继承的类需要能够为调用DoStuff()时可以接受的选项指定方案。

我从这样的抽象GetOptionsSchema()方法开始:

public abstract class Widget
{
    public abstract OptionsSchema GetOptionsSchema();
    public abstract void DoStuff(Options options);
}

然后,其他开发人员将通过创建自定义Widget类型来扩展我的框架:

public abstract class FooWidget: Widget
{
    public overide DoStuff(Options options)
    {
        //Do some FooWidget stuff
    }

    public overide OptionsSchema GetOptionsSchema()
    {
        //Return options for FooWidget
    }
}

这可行,但是需要框架为每种Widget类型创建一个实例,以确定它们接受的选项架构,即使实际上不需要使用任何这些类型的DoStuff()也不例外。

最终,我希望能够直接从System.Type确定特定Widget类型的选项架构。我将创建一个自定义的OptionsSchema属性,但是构造这些模式会更加复杂,然后在属性的构造函数中进行有意义的设置。它需要通过一种方法来实现。

我已经看到其他框架通过创建自定义属性来解决类似问题,该自定义属性通过名称标识静态方法或属性。例如,NUnit中的TestCaseSource属性。

以下是此选项的外观:

public abstract class Widget
{
    public abstract void DoStuff(Options options);
}

[OptionsSchemaSource(nameof(GetOptionsSchema))]
public abstract class FooWidget: Widget
{
    public overide DoStuff(Options options)
    {
        //Do some FooWidget stuff
    }

    public static OptionSchema GetOptionsSchema()
    {
        //Return options for FooWidget
    }
}

我喜欢OptionsSchemaSource属性如何使直接从System.Type获取选项架构成为可能,但是对于其他创建自定义Widget类型的开发人员来说,这似乎也很难发现。

使用抽象方法,另一个Widget开发人员知道他们必须重写GetOptionSchema(),因为他们的代码否则将无法编译。使用OptionsSchemaSource属性,我所能做的最好的就是希望人们阅读我的文档,并让框架在运行时抛出异常,如果它遇到没有OptionsSchemaSource属性的Widget。

是否有替代/更好/推荐的方法?

2 个答案:

答案 0 :(得分:1)

当前无法在编译时强制执行该属性;对于您的用例而言,这将是理想的选择。也不可能有abstract static方法,或者在接口中指定静态方法。因此,除了通过抽象类或接口(需要访问该类型的实例)强制实例方法外,无法确保该方法在编译时确实存在。

我会使用属性的想法-期望开发人员阅读文档不是没有道理的;即使覆盖了抽象方法,开发人员也需要知道如何在覆盖的方法中构造OptionSchema-回到文档!

答案 1 :(得分:1)

您几乎已经知道所有有趣的事物,可以判断哪种方法最好。

如前所述,您不能在类型上定义静态接口,因此无法确保强制添加新的开发人员来添加属性。

因此,您确定的两个选择是我能想到的仅有的两个。 现在,让我们做一个利弊并尝试加强它们。

属性

您可以减轻确保开发人员将属性放在有意义的错误消息上的麻烦。我要说的是,您应该仅基于属性而不是继承来管理类的发现。 如果您使用属性管理所有内容,则无需从Widget继承。 这是一个专家,因为现在每个人都可以继承(如果需要),并且可以重新实现(如果愿意)。

缺点是可发现性的实现将更加复杂:您将需要在启动时使用反射,获取MethodInfo,检查该方法是否具有正确的签名,以防万一并给出适当的错误并调用该方法根据需要将结果拆箱。

考虑一下:您想要一个静态方法,因为您不需要实例化一个类型化的Widget实例,但是实际上实例化一个新的Widget可能不是什么大问题。

抽象类

好吧,您对开发人员执行了一个继承链,这可能是好的,必要的或完全可选的(您认为),但是您会获得自我记录的经验。

明显的缺点是,在启动时,您需要为发现的每个派生类型实例化一个Widget,但与程序集扫描和类型检查以及方法反射的发现和通过反射的方法调用相比,这很可能是花生。 丑陋?的种类。效率低下?没那么多。最终用户看不到它的代码。

恕我直言

在设计框架时,我想在将一些“丑陋的”代码放入框架中时是一个很好的权衡,如果这意味着使用该库的每个实现都会少一点好一点。

总而言之,如果您要设计一个灵活且易于发现的库,则应该期望开发人员至少阅读一份快速入门指南。如果他们可以在5分钟内阅读一点信息(“扩展基类”或“添加一个或几个属性”),而这一点为他们提供了一个方向,以发现小部件注册的各个方面,好的:你真的不能比这更好。

我的电话:我会带着一点小小的警告走抽象类路线。我真的不喜欢拥有一个强制的基类。因此,我将在启动时基于界面IWidget来组织发现,该界面包含GetOptionsSchema方法,并且使用该小部件所需的一切(可能是DoStuff方法,但很可能是其他)。在启动时,您搜索的接口的实现不是抽象的,您可以使用。

如果,并且仅当您确实需要预先提供的唯一位是字符串或其他类似的简单类型时,我需要附加属性。

[OptionsSchemaName("http://something")]
public class MyWidget : WidgetBase
{
    public overide DoStuff(Options options)
    {
        //Do some FooWidget stuff
    }

    public static OptionSchema GetOptionsSchema()
    {
        //Return options for FooWidget
    }
}

然后,您的类型发现基础结构可以搜索非抽象的IWidget,并在启动时像the type MyWidget is lacking an OptionsSchemaName attribute. Every implementation of IWidget must define one. See http://mydocs for information一样抛出有意义的错误。

ang!钉了!