我们如何要求没有参数的构造函数来实现接口的类型?

时间:2020-03-05 18:43:02  来源:igfitidea点击:

有办法吗?

我需要所有实现特定接口的类型都具有无参数构造函数,这可以完成吗?

我正在为公司中的其他开发人员开发用于特定项目的基本代码。

有一个过程会创建执行特定任务的类型实例(在不同线程中),而我需要这些类型遵循特定协定(ergo,接口)。

该接口将在装配体内部

如果我们对不带接口的这种情况有建议,我将很乐意将其考虑在内...

解决方案

回答

我不这么认为。

我们也不能为此使用抽象类。

回答

不要太直言不讳,但是我们误解了接口的目的。

一个接口意味着几个人可以在自己的类中实现它,然后将这些类的实例传递给要使用的其他类。创造会产生不必要的强烈耦合。

听起来我们确实需要某种注册系统,或者让人们注册实现该接口的可用类的实例,或者是可以根据要求创建所述项目的工厂。

回答

胡安

不幸的是,没有办法用强类型的语言解决这个问题。我们将无法确保在编译时可以通过基于Activator的代码实例化这些类。

(ed:删除了错误的替代解决方案)

不幸的是,原因是无法将接口,抽象类或者虚拟方法与构造函数或者静态方法结合使用。简短的原因是,前者不包含显式类型信息,而后者则需要显式类型信息。

构造函数和静态方法在调用时必须具有显式(在代码中此位置)类型信息。这是必需的,因为没有涉及到的类的实例,运行时可以查询该实例以获得基础类型,运行时需要该基础类型来确定要调用的实际方法。

接口,抽象类或者虚拟方法的整个要点都应能够在没有显式类型信息的情况下进行函数调用,这是由于存在以下事实而导致的:所引用的实例没有"隐藏"类型信息直接提供给调用代码。因此,这两种机制非常简单地相互排斥。它们不能一起使用,因为当我们混合它们时,最终在任何地方都没有任何具体的类型信息,这意味着运行时不知道在哪里可以找到我们要调用的函数。

回答

我想提醒大家:

  • 在.NET中编写属性很容易
  • 轻松地在.NET中编写确保符合公司标准的静态分析工具

编写工具以获取实现特定接口/具有属性的所有具体类,并验证其具有无参数构造函数,大约需要5分钟的编码时间。我们将其添加到构建后步骤中,现在我们有了一个框架,可用于执行任何其他静态分析。

语言,编译器,IDE,大脑都是它们的工具。使用它们!

回答

我们可以使用类型参数约束

interface ITest<T> where T: new()
{
    //...
}

class Test: ITest<Test>
{
    //...
}

回答

不,你不能那样做。也许对于情况,工厂界面会有所帮助?就像是:

interface FooFactory {
    Foo createInstance();
}

对于Foo的每个实现,我们都将创建一个FooFactory实例,该实例知道如何创建它。

回答

我们不需要为激活器实例化类的无参数构造函数。我们可以使用参数化的构造函数,并从Activator传递所有参数。在此查看MSDN。

回答

胡安·曼努埃尔(Juan Manuel)说:

that's one of the reasons I don't understand why it cannot be a part of the contract in the interface

这是一种间接机制。泛型允许我们"作弊"并随接口一起发送类型信息。这里要记住的关键是,约束不在我们直接使用的接口上。这不是对接口本身的约束,而是对将在接口上"顺风顺水"的其他类型的约束。恐怕,这是我能提供的最好的解释。

通过说明这一事实,我将指出在aku的代码中发现的一个漏洞。可以编写一个可以正常编译但在尝试实例化该类时却在运行时失败的类:

public class Something : ITest<String>
{
  private Something() { }
}

有些东西是从ITest <T>派生的,但没有实现无参数构造函数。它将编译良好,因为String确实实现了无参数构造函数。同样,约束是对T的约束,因此对String的约束,而不是对ITest或者Something的约束。由于满足了对T的约束,因此将进行编译。但是它将在运行时失败。

为防止出现此问题的某些情况,我们需要向T添加另一个约束,如下所示:

public interface ITest<T>
  where T : ITest<T>, new()
{
}

注意新的约束:T:ITest <T>。此约束指定传递给ITest <T>的参数参数的内容也必须从ITest <T>派生。

即使这样,仍不能防止所有情况的破洞。下面的代码可以正常编译,因为A具有无参数的构造函数。但是由于B的无参数构造函数是私有的,因此用进程实例化B将在运行时失败。

public class A : ITest<A>
{
}

public class B : ITest<A>
{
  private B() { }
}

回答

用该类型调用RegisterType方法,并使用泛型对其进行约束。然后,与其遍历程序集来查找ITest实现器,不如将它们存储并从那里创建。

void RegisterType<T>() where T:ITest, new() {
}

回答

因此,我们需要一种可以创建实现接口的未知类型实例的东西。我们基本上有三个选择:一个工厂对象,一个Type对象或者一个委托。这是给定的:

public interface IInterface
{
    void DoSomething();
}

public class Foo : IInterface
{
    public void DoSomething() { /* whatever */ }
}

使用Type非常丑陋,但在某些情况下有意义:

public IInterface CreateUsingType(Type thingThatCreates)
{
    ConstructorInfo constructor = thingThatCreates.GetConstructor(Type.EmptyTypes);
    return (IInterface)constructor.Invoke(new object[0]);
}

public void Test()
{
    IInterface thing = CreateUsingType(typeof(Foo));
}

它的最大问题是,在编译时,我们不能保证Foo实际上具有默认构造函数。而且,如果碰巧是性能关键代码,则反射会有点慢。

最常见的解决方案是使用工厂:

public interface IFactory
{
    IInterface Create();
}

public class Factory<T> where T : IInterface, new()
{
    public IInterface Create() { return new T(); }
}

public IInterface CreateUsingFactory(IFactory factory)
{
    return factory.Create();
}

public void Test()
{
    IInterface thing = CreateUsingFactory(new Factory<Foo>());
}

在上面,IFactory才是真正重要的。对于只是提供默认构造函数的类,Factory只是一个便利类。这是最简单且通常是最佳的解决方案。

第三种当前不常见但有可能成为更常见的解决方案是使用委托:

public IInterface CreateUsingDelegate(Func<IInterface> createCallback)
{
    return createCallback();
}

public void Test()
{
    IInterface thing = CreateUsingDelegate(() => new Foo());
}

这样做的好处是代码简短而简单,可以与任何构造方法一起使用,并且(使用闭包)使我们可以轻松传递构造对象所需的其他数据。