定义构造函数签名的接口?

Bor*_*ens 526 c# constructor interface

这是我第一次碰到这个问题,这很奇怪,但是:

如何在C#接口中定义构造函数?

编辑
有些人想要一个例子(这是一个空闲时间项目,所以是的,这是一个游戏)

IDrawable
+ Update
+ Draw

为了能够更新(检查屏幕边缘等)并绘制自己,它总是需要一个GraphicsDeviceManager.所以我想确保对象有引用它.这将属于构造函数.

现在,我写下来我想我在这里实施的IObservableGraphicsDeviceManager应该采取的IDrawable......看来要么我不明白的XNA框架或框架不是想出来的非常好.

编辑
在接口的上下文中,我对构造函数的定义似乎有些混乱.实际上不能实例化接口,因此不需要构造函数.我想要定义的是构造函数的签名.正如接口可以定义某个方法的签名,接口可以定义构造函数的签名.

Jon*_*eet 313

你不能.它偶尔会很痛苦,但无论如何你都无法使用普通技术来调用它.

在博客文章中,我建议静态接口只能在泛型类型约束中使用 - 但可能非常方便,IMO.

关于如果你可以在一个接口中定义一个构造函数的一点,你在导出类时遇到了麻烦:

public class Foo : IParameterlessConstructor
{
    public Foo() // As per the interface
    {
    }
}

public class Bar : Foo
{
    // Yikes! We now don't have a parameterless constructor...
    public Bar(int x)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 是的但是有什么问题,没有合适的Bar构造函数,因为它不能正确地满足接口.这就像说你不能在接口中定义方法,因为如果你不实现它,它将无法工作. (61认同)
  • @Gravitas:有用与否,今天肯定无法使用.我怀疑如果这个功能出现,它需要比我们在评论中做的更精心的设计:) (10认同)
  • @ user1721649:我有很多地方想要这个 - 几乎总是有通用约束.基本上,您希望能够在泛型方法中调用构造函数,以创建实现某个接口的泛型类型的实例.它确实*会很有用. (10认同)
  • 我确实可以看到问题,但您定义的所有其他方法也是如此.通常NotSupportedException是唯一的出路. (5认同)
  • @boris:不同之处在于,在编译器保证的情况下,始终使用正常继承调用**.在这种情况下,有一些"应该"存在但不存在. (5认同)
  • @jsimmons:不,你错过了我的观点.使用普通接口,从实现接口的类型派生的任何类型也会自动实现接口.那不是这种情况. (5认同)
  • 我明白了,但是那会是个问题吗?这只是意味着作为继承的一部分,您将需要实现某个构造函数。我的意思是,毕竟所有构建都是接口要定义的公共API的一部分。除非它暗示了编译器中更大的问题? (2认同)
  • @jsimmons:嗯,这意味着类型在第一次发布后无法实现新接口,因为它可能会破坏现有的子类型.这些并非不可克服,但它们是需要考虑的额外事项 - 目前尚不存在的问题. (2认同)

Ger*_*old 135

一个非常晚的贡献,展示了接口构造函数的另一个问题.(我选择这个问题是因为它对问题有最明确的阐述).假设我们可以:

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

class Person : IPerson, ICustomer
{
    Person(string name) { }
    Person(DateTime registrationDate) { }
}
Run Code Online (Sandbox Code Playgroud)

按惯例,"接口构造函数"的实现由类型名称替换.

现在创建一个实例:

ICustomer a = new Person("Ernie");
Run Code Online (Sandbox Code Playgroud)

我们会说合同ICustomer是否得到遵守?

那怎么样:

interface ICustomer
{
    ICustomer(string address);
}
Run Code Online (Sandbox Code Playgroud)

  • 如果在ICustomer和IPerson中有相同签名和名称的方法,那么这个问题也存在.我不明白这有多大帮助.接口不是"仅我的定义".这是为了"不惜一切代价包括我" (14认同)
  • 我不知道这是一个什么问题,人们可以简单地制定一个规则,只允许链接具有相同构造函数签名的接口.这将是接口A实现"foo:int"而接口B实现"foo:string"的行为,它们只是不兼容. (8认同)
  • @nawfal重点是接口永远不要求方法是_executed_,只是它应该存在.它永远不能保证国家.相反,"构造函数接口"确实要求在构造对象时完成某些事情(执行).当存在不同的接口时,永远不能保证这一点. (7认同)
  • @GertArnold一个方法完成它的工作,而一个构造函数完成它的工作.我不明白这里有什么区别.接口签订了"我的实现应该在那里"的合同,而不是像"我的应该是唯一的实现".我会说为了一致性,这应该对构造函数,方法,属性有效 (5认同)
  • 它让我想起没有虚拟继承的c ++多重继承问题 (2认同)
  • +1,虽然相当模糊,但这可能是最好的解释为什么接口,不仅在C#中,而且在许多语言(如Java)中,都不能有构造函数成员. (2认同)

Dan*_*Dan 131

如前所述,您不能在接口上拥有构造函数.但是由于大约7年后谷歌的排名很高,我认为我会在这里筹码 - 特别是为了展示你如何使用抽象基类与你现有的界面相结合,并可能减少重构的数量未来需要类似的情况.这个概念已经在一些评论中被暗示过,但我认为值得展示如何实际做到这一点.

所以到目前为止你的主界面看起来像这样:

public interface IDrawable
{
    void Update();
    void Draw();
}
Run Code Online (Sandbox Code Playgroud)

现在使用要强制执行的构造函数创建一个抽象类.实际上,既然它从你编写原始问题开始就可以使用了,我们可以在这里稍微看一下并在这种情况下使用泛型,这样我们就可以将其调整到可能需要相同功能但具有不同构造函数要求的其他接口:

public abstract class MustInitialize<T>
{
    public MustInitialize(T parameters)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您需要创建一个继承自IDrawable接口和MustInitialize抽象类的新类:

public class Drawable : MustInitialize<GraphicsDeviceManager>, IDrawable
{
    GraphicsDeviceManager _graphicsDeviceManager;

    public Drawable(GraphicsDeviceManager graphicsDeviceManager)
        : base (graphicsDeviceManager)
    {
        _graphicsDeviceManager = graphicsDeviceManager;
    }

    public void Update()
    {
        //use _graphicsDeviceManager here to do whatever
    }

    public void Draw()
    {
        //use _graphicsDeviceManager here to do whatever
    }
}
Run Code Online (Sandbox Code Playgroud)

然后只需创建一个Drawable实例,你就可以了:

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);
Run Code Online (Sandbox Code Playgroud)

这里很酷的是我们创建的新Drawable类的行为与我们对IDrawable的期望一样.

如果需要将多个参数传递给MustInitialize构造函数,则可以创建一个类,该类定义您需要传入的所有字段的属性.

  • 重要的是要强调,您不能创建"MustInitialize"类来涵盖每种情况,因为C#不允许多重继承.这意味着如果您的类继承了一个抽象类,那么它也不能继承另一个类. (9认同)
  • 这是真的@Skarlot,这不是灵丹妙药。不过,在您指出的问题中,希望直接修改您已经继承的抽象类是有意义的。但是仍然存在不可能和/或不合适的情况,因此需要更深入的设计模式。 (4认同)
  • @andrew pate - 正如 Skarllot 所指出的,多重继承确实如此,但在 OP 的这个特定情况下,这不是一个问题。尽管您提出的建议可行,但我不建议编写一个需要调用公共方法才能正确使用的类。如果您不熟悉这个术语,请搜索“Temporal Coupling”。 (2认同)

Mic*_*ael 62

你不能.

接口定义其他对象实现的合同,因此没有需要初始化的状态.

如果您有一些需要初始化的状态,则应考虑使用抽象基类.

  • 因为合同会约束你提供某种_behaviour_.如何使用接口意味着提取常见行为,而不依赖于状态(这将是一个实现细节). (15认同)
  • 为什么合同没有州? (11认同)

Jer*_*eer 20

创建一个定义构造函数的接口不可能的,但是可以定义一个强制类型具有无参数构造函数的接口,尽管它是一个使用泛型的非常难看的语法...我实际上不太确定它确实是一个很好的编码模式.

public interface IFoo<T> where T : new()
{
  void SomeMethod();
}

public class Foo : IFoo<Foo>
{
  // This will not compile
  public Foo(int x)
  {

  }

  #region ITest<Test> Members

  public void SomeMethod()
  {
    throw new NotImplementedException();
  }

  #endregion
}
Run Code Online (Sandbox Code Playgroud)

另一方面,如果要测试类型是否具有无参数构造函数,则可以使用反射来执行此操作:

public static class TypeHelper
{
  public static bool HasParameterlessConstructor(Object o)
  {
    return HasParameterlessConstructor(o.GetType());
  }

  public static bool HasParameterlessConstructor(Type t)
  {
    // Usage: HasParameterlessConstructor(typeof(SomeType))
    return t.GetConstructor(new Type[0]) != null;
  }
}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.


Jer*_*eer 20

我回头看着这个问题,我想,也许我们正在以错误的方式解决这个问题.当涉及使用某些参数定义构造函数时,接口可能不是可行的方法......但是(抽象)基类是.

如果你在那里创建一个带有构造函数的基类,它接受你需要的参数,那么从中获取的每个类都需要提供它们.

public abstract class Foo
{
  protected Foo(SomeParameter x)
  {
    this.X = x;
  }

  public SomeParameter X { get; private set }
}

public class Bar : Foo // Bar inherits from Foo
{
  public Bar() 
    : base(new SomeParameter("etc...")) // Bar will need to supply the constructor param
  {
  }
}
Run Code Online (Sandbox Code Playgroud)


JTt*_*eek 6

我发现解决这个问题的一种方法是将建筑分隔成一个单独的工厂.例如,我有一个名为IQueueItem的抽象类,我需要一种方法将该对象转换为另一个对象(CloudQueueMessage).所以在IQueueItem界面上我有 -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}
Run Code Online (Sandbox Code Playgroud)

现在,我还需要一种方法让我的实际队列类将CloudQueueMessage转换回IQueueItem - 即需要静态构造,如IQueueItem objMessage = ItemType.FromMessage.相反,我定义了另一个接口IQueueFactory -

public interface IQueueItemFactory<T> where T : IQueueItem
{
    T FromMessage(CloudQueueMessage objMessage);
}
Run Code Online (Sandbox Code Playgroud)

现在我终于可以在没有new()约束的情况下编写我的通用队列类,在我看来这是主要问题.

public class AzureQueue<T> where T : IQueueItem
{
    private IQueueItemFactory<T> _objFactory;
    public AzureQueue(IQueueItemFactory<T> objItemFactory)
    {
        _objFactory = objItemFactory;
    }


    public T GetNextItem(TimeSpan tsLease)
    {
        CloudQueueMessage objQueueMessage = _objQueue.GetMessage(tsLease);
        T objItem = _objFactory.FromMessage(objQueueMessage);
        return objItem;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我可以创建一个满足我标准的实例

 AzureQueue<Job> objJobQueue = new JobQueue(new JobItemFactory())
Run Code Online (Sandbox Code Playgroud)

希望有一天这可以帮助其他人,显然很多内部代码被删除,试图显示问题和解决方案


Ces*_*sar 6

解决此问题的一种方法是利用泛型和 new() 约束。

您可以将构造函数表示为工厂类/接口,而不是将其表示为方法/函数。如果您在需要创建类对象的每个调用站点上指定 new() 通用约束,您将能够相应地传递构造函数参数。

对于您的 IDrawable 示例:

public interface IDrawable
{
    void Update();
    void Draw();
}

public interface IDrawableConstructor<T> where T : IDrawable
{
    T Construct(GraphicsDeviceManager manager);
}


public class Triangle : IDrawable
{
    public GraphicsDeviceManager Manager { get; set; }
    public void Draw() { ... }
    public void Update() { ... }
    public Triangle(GraphicsDeviceManager manager)
    {
        Manager = manager;
    }
}


public TriangleConstructor : IDrawableConstructor<Triangle>
{
    public Triangle Construct(GraphicsDeviceManager manager)
    {
        return new Triangle(manager);
    } 
}
Run Code Online (Sandbox Code Playgroud)

现在当你使用它时:

public void SomeMethod<TBuilder>(GraphicsDeviceManager manager)
  where TBuilder: IDrawableConstructor<Triangle>, new()
{
    // If we need to create a triangle
    Triangle triangle = new TBuilder().Construct(manager);

    // Do whatever with triangle
}
Run Code Online (Sandbox Code Playgroud)

您甚至可以使用显式接口实现将所有创建方法集中在一个类中:

public DrawableConstructor : IDrawableConstructor<Triangle>,
                             IDrawableConstructor<Square>,
                             IDrawableConstructor<Circle>
{
    Triangle IDrawableConstructor<Triangle>.Construct(GraphicsDeviceManager manager)
    {
        return new Triangle(manager);
    } 

    Square IDrawableConstructor<Square>.Construct(GraphicsDeviceManager manager)
    {
        return new Square(manager);
    } 

    Circle IDrawableConstructor<Circle>.Construct(GraphicsDeviceManager manager)
    {
        return new Circle(manager);
    } 
}
Run Code Online (Sandbox Code Playgroud)

要使用它:

public void SomeMethod<TBuilder, TShape>(GraphicsDeviceManager manager)
  where TBuilder: IDrawableConstructor<TShape>, new()
{
    // If we need to create an arbitrary shape
    TShape shape = new TBuilder().Construct(manager);

    // Do whatever with the shape
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是使用 lambda 表达式作为初始值设定项。在调用层次结构早期的某个时刻,您将知道需要实例化哪些对象(即,当您创建或获取对 GraphicsDeviceManager 对象的引用时)。一旦你拥有它,就传递 lambda

() => new Triangle(manager) 
Run Code Online (Sandbox Code Playgroud)

到后续方法,以便他们从那时起知道如何创建三角形。如果您无法确定您需要的所有可能方法,您始终可以创建一个使用反射实现 IDrawable 的类型的字典,并在字典中注册上面显示的 lambda 表达式,您可以将其存储在共享位置或传递给进一步的函数调用。


Mat*_*hew 5

通用工厂方法似乎仍然是理想的.您会知道工厂需要一个参数,而这些参数会被传递给正在实例化的对象的构造函数.

注意,这只是语法验证的伪代码,可能有一个运行时警告我在这里缺少:

public interface IDrawableFactory
{
    TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager) 
              where TDrawable: class, IDrawable, new();
}

public class DrawableFactory : IDrawableFactory
{
    public TDrawable GetDrawingObject<TDrawable>(GraphicsDeviceManager graphicsDeviceManager) 
                     where TDrawable : class, IDrawable, new()
    {
        return (TDrawable) Activator
                .CreateInstance(typeof(TDrawable), 
                                graphicsDeviceManager);
    }

}

public class Draw : IDrawable
{
 //stub
}

public class Update : IDrawable {
    private readonly GraphicsDeviceManager _graphicsDeviceManager;

    public Update() { throw new NotImplementedException(); }

    public Update(GraphicsDeviceManager graphicsDeviceManager)
    {
        _graphicsDeviceManager = graphicsDeviceManager;
    }
}

public interface IDrawable
{
    //stub
}
public class GraphicsDeviceManager
{
    //stub
}
Run Code Online (Sandbox Code Playgroud)

可能的用法示例:

    public void DoSomething()
    {
        var myUpdateObject = GetDrawingObject<Update>(new GraphicsDeviceManager());
        var myDrawObject = GetDrawingObject<Draw>(null);
    }
Run Code Online (Sandbox Code Playgroud)

当然,您只需要通过工厂的创建实例来保证您始终拥有适当的初始化对象.也许使用像AutoFac这样的依赖注入框架是有意义的; Update()可以"询问"IoC容器中的新GraphicsDeviceManager对象.