关于C#:定义构造函数签名的接口?

Interface defining a constructor signature?

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

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

编辑有些人想要一个例子(这是一个业余项目,所以是的,这是一个游戏)

易懂的+更新+拉伸

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

既然我写下来了,我想我在这里实施的是IObservableGraphicsDeviceManager应该采用IDrawable。似乎我没有得到XNA框架,或者这个框架没有被很好地考虑。

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


你不能。这偶尔是一种痛苦,但你无论如何也不能用正常的方法来形容它。

在一篇博文中,我建议使用静态接口,它只能在泛型类型约束中使用——但可能非常方便,IMO。

一点是,如果可以在接口内定义构造函数,那么在派生类时会遇到困难:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
    {
    }
}


一个非常晚的贡献演示了与接口构造函数的另一个问题。(我选择这个问题是因为它有最清晰的问题表达)。假设我们可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

class Person : IPerson, ICustomer
{
    Person(string name) { }
    Person(DateTime registrationDate) { }
}

其中,按照惯例,"接口构造函数"的实现被类型名替换。

现在创建一个实例:

1
ICustomer a = new Person("Ernie");

我们可以说合同1(0)是遵守的吗?

那么这个呢:

1
2
3
4
interface ICustomer
{
    ICustomer(string address);
}


如前所述,在接口上不能有构造函数。但是,由于这是Google 7年后的一个排名如此之高的结果,我想我会在这里插手——特别是展示如何将抽象基类与现有接口结合使用,并可能减少将来在类似情况下所需的重构量。在一些评论中已经暗示了这个概念,但我认为值得展示一下如何真正做到这一点。

因此,您的主界面目前看起来是这样的:

1
2
3
4
5
public interface IDrawable
{
    void Update();
    void Draw();
}

现在,使用要强制实施的构造函数创建一个抽象类。实际上,自从您编写原始问题以来,它现在就可用了,因此我们可以在这里稍加花哨,并在这种情况下使用泛型,这样我们就可以将其适应可能需要相同功能但具有不同构造函数要求的其他接口:

1
2
3
4
5
6
7
public abstract class MustInitialize<T>
{
    public MustInitialize(T parameters)
    {

    }
}

现在,您需要创建一个从idrawable接口和mustinitialize抽象类继承的新类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
    }
}

然后只需创建一个drawable实例就可以了:

1
IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

这里很酷的一点是,我们创建的新Drawable类的行为仍然像我们期望从IDrawable中得到的那样。

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


你不能。

接口定义其他对象实现的契约,因此没有需要初始化的状态。

如果有一些状态需要初始化,则应考虑改用抽象基类。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
  }
}

希望这有帮助。


我回头看这个问题,心里想,也许我们是以错误的方式解决这个问题。当涉及到用某些参数定义一个构造函数时,接口可能不是一种方法…但是(抽象的)基类是。

如果您创建了一个基础类,上面有一个接受您需要的参数的构造函数,那么从中派生的每个类都需要提供这些参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
  {
  }
}


我发现解决这个问题的一个方法是把建筑分离成一个独立的工厂。例如,我有一个名为iqueueitem的抽象类,我需要一种方法来将该对象与另一个对象(cloudqueuemessage)相互转换。所以在接口iqueitem上-

1
2
3
4
public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

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

1
2
3
4
public interface IQueueItemFactory<T> where T : IQueueItem
{
    T FromMessage(CloudQueueMessage objMessage);
}

现在,我终于可以在不使用new()约束的情况下编写通用队列类了,在我的例子中,这是主要的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
    }
}

现在我可以创建一个满足条件的实例

1
 AzureQueue<Job> objJobQueue = new JobQueue(new JobItemFactory())

希望有一天这能帮助其他人,很明显,为了展示问题和解决方案,删除了很多内部代码。


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
}

可能的用法示例:

1
2
3
4
5
    public void DoSomething()
    {
        var myUpdateObject = GetDrawingObject<Update>(new GraphicsDeviceManager());
        var myDrawObject = GetDrawingObject<Draw>(null);
    }

当然,您只希望通过工厂创建实例,以确保始终具有适当初始化的对象。也许使用像autofac这样的依赖注入框架是有意义的;update()可以"询问"ioc容器以获得新的graphicsDeviceManager对象。


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

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

对于您的idrawable示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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);
    }
}

现在,当您使用它时:

1
2
3
4
5
6
7
8
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
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
    }
}

使用它:

1
2
3
4
5
6
7
8
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
}

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

1
() => new Triangle(manager)

接下来的方法,这样他们将知道如何从那时起创建三角形。如果无法确定所需的所有可能方法,则可以始终使用反射创建实现IDrawable的类型字典,并在字典中注册上面显示的lambda表达式,该字典可以存储在共享位置,也可以传递给其他函数调用。


你可以用仿制药的把戏来做到这一点,但它仍然容易受到乔恩·斯基特所写的影响:

1
2
3
public interface IHasDefaultConstructor<T> where T : IHasDefaultConstructor<T>, new()
{
}

实现此接口的类必须具有无参数构造函数:

1
2
3
4
public class A : IHasDefaultConstructor<A> //Notice A as generic parameter
{
    public A(int a) { } //compile time error
}


接口的目的是强制某个对象签名。它应该明确地不关心对象如何在内部工作。因此,从概念的角度来看,接口中的构造函数并没有真正意义。

但也有一些选择:

  • 创建用作最小默认实现的抽象类。该类应该具有您期望的实现类的构造函数拥有。

  • 如果你不介意过度杀戮,使用abstractFactory模式和在工厂类接口中声明具有所需的签名。

  • GraphicsDeviceManager作为参数传递给UpdateDraw方法。

  • 使用复合面向对象编程框架将GraphicsDeviceManager传递到需要它的对象部分。在我看来,这是一个非常实验性的解决方案。

一般来说,你描述的情况不容易处理。类似的情况是业务应用程序中需要访问数据库的实体。


我用下面的模式使它防弹。

  • 从基派生类的开发人员不能意外地创建公共可访问的构造函数
  • 最后一个类开发人员必须通过通用的创建方法
  • 一切都是类型安全的,不需要铸件
  • 它100%灵活,可以在任何地方重复使用,在那里您可以定义自己的基础班级。
  • 尝试一下,如果不修改基类(除了如果定义了一个过时的标志,但没有将错误标志设置为"真",那么即使这样,最终也会出现一个警告)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
        public abstract class Base<TSelf, TParameter>
        where TSelf : Base<TSelf, TParameter>, new()
    {
        protected const string FactoryMessage ="Use YourClass.Create(...) instead";
        public static TSelf Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);

            return me;
        }

        [Obsolete(FactoryMessage, true)]
        protected Base()
        {
        }



        protected virtual void Initialize(TParameter parameter)
        {

        }
    }

    public abstract class BaseWithConfig<TSelf, TConfig>: Base<TSelf, TConfig>
        where TSelf : BaseWithConfig<TSelf, TConfig>, new()
    {
        public TConfig Config { get; private set; }

        [Obsolete(FactoryMessage, true)]
        protected BaseWithConfig()
        {
        }
        protected override void Initialize(TConfig parameter)
        {
            this.Config = parameter;
        }
    }

    public class MyService : BaseWithConfig<MyService, (string UserName, string Password)>
    {
        [Obsolete(FactoryMessage, true)]
        public MyService()
        {
        }
    }

    public class Person : Base<Person, (string FirstName, string LastName)>
    {
        [Obsolete(FactoryMessage,true)]
        public Person()
        {
        }

        protected override void Initialize((string FirstName, string LastName) parameter)
        {
            this.FirstName = parameter.FirstName;
            this.LastName = parameter.LastName;
        }

        public string LastName { get; private set; }

        public string FirstName { get; private set; }
    }



    [Test]
    public void FactoryTest()
    {
        var notInitilaizedPerson = new Person(); // doesn't compile because of the obsolete attribute.
        Person max = Person.Create(("Max","Mustermann"));
        Assert.AreEqual("Max",max.FirstName);

        var service = MyService.Create(("MyUser","MyPassword"));
        Assert.AreEqual("MyUser", service.Config.UserName);
    }

编辑:下面是一个基于绘图示例的示例,它甚至强制执行接口抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
        public abstract class BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithAbstraction<TSelf, TInterface, TParameter>, TInterface, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected BaseWithAbstraction()
        {
        }

        protected const string FactoryMessage ="Use YourClass.Create(...) instead";
        public static TInterface Create(TParameter parameter)
        {
            var me = new TSelf();
            me.Initialize(parameter);

            return me;
        }

        protected virtual void Initialize(TParameter parameter)
        {

        }
    }



    public abstract class BaseWithParameter<TSelf, TInterface, TParameter> : BaseWithAbstraction<TSelf, TInterface, TParameter>
        where TSelf : BaseWithParameter<TSelf, TInterface, TParameter>, TInterface, new()
    {
        protected TParameter Parameter { get; private set; }

        [Obsolete(FactoryMessage, true)]
        protected BaseWithParameter()
        {
        }
        protected sealed override void Initialize(TParameter parameter)
        {
            this.Parameter = parameter;
            this.OnAfterInitialize(parameter);
        }

        protected virtual void OnAfterInitialize(TParameter parameter)
        {
        }
    }


    public class GraphicsDeviceManager
    {

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

    internal abstract class Drawable<TSelf> : BaseWithParameter<TSelf, IDrawable, GraphicsDeviceManager>, IDrawable
        where TSelf : Drawable<TSelf>, IDrawable, new()
    {
        [Obsolete(FactoryMessage, true)]
        protected Drawable()
        {
        }

        public abstract void Update();
        public abstract void Draw();
    }

    internal class Rectangle : Drawable<Rectangle>
    {
        [Obsolete(FactoryMessage, true)]
        public Rectangle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }
    internal class Circle : Drawable<Circle>
    {
        [Obsolete(FactoryMessage, true)]
        public Circle()
        {
        }

        public override void Update()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }

        public override void Draw()
        {
            GraphicsDeviceManager manager = this.Parameter;
            // TODo  manager
        }
    }


    [Test]
    public void FactoryTest()
    {
        // doesn't compile because interface abstraction is enforced.
        Rectangle rectangle = Rectangle.Create(new GraphicsDeviceManager());

        // you get only the IDrawable returned.
        IDrawable service = Circle.Create(new GraphicsDeviceManager());
    }

强制某种类型的构造函数的一种方法是只在接口中声明Getters,这可能意味着实现类必须有一个方法(理想情况下是一个构造函数)才能为其设置值(private)。


如果可以在接口中定义构造函数,这将非常有用。

假设一个接口是一个必须以指定方式使用的契约。对于某些情况,以下方法可能是一种可行的替代方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public interface IFoo {

    /// <summary>
    /// Initialize foo.
    /// </summary>
    /// <remarks>
    /// Classes that implement this interface must invoke this method from
    /// each of their constructors.
    /// </remarks>
    /// <exception cref="InvalidOperationException">
    /// Thrown when instance has already been initialized.
    /// </exception>
    void Initialize(int a);

}

public class ConcreteFoo : IFoo {

    private bool _init = false;

    public int b;

    // Obviously in this case a default value could be used for the
    // constructor argument; using overloads for purpose of example

    public ConcreteFoo() {
        Initialize(42);
    }

    public ConcreteFoo(int a) {
        Initialize(a);
    }

    public void Initialize(int a) {
        if (_init)
            throw new InvalidOperationException();
        _init = true;

        b = a;
    }

}


虽然您不能在接口中定义构造函数签名,但我觉得值得一提的是,这可能是一个考虑抽象类的地方。抽象类可以以与接口相同的方式定义未实现(抽象)方法签名,但也可以实现(具体)方法和构造函数。

缺点是,因为它是类的一种类型,所以它不能用于接口可以使用的任何多继承类型方案。


你没有。

构造函数是可以实现接口的类的一部分。接口只是类必须实现的方法的契约。


接口是协定接口,不允许字段,即不需要初始化任何内容。接口只保留引用。接口必须要求派生类保留内存。


如果我正确理解了op,我们希望强制一个契约,其中graphicsDeviceManager总是通过实现类初始化的。我也遇到过类似的问题,我正在寻找更好的解决方案,但这是我能想到的最好的方法:

向接口中添加一个setgraphicsDeviceManager(graphicsDeviceManager gdo),这样,实现类将被迫编写一个逻辑,该逻辑将需要来自构造函数的调用。