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

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

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

IDrawable
+更新
+画画

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

既然我写下了这个,我想我在这里实现的是 IObservableGraphicsDeviceManager 应采取 IDrawable...看来要么我没有得到 XNA 框架,要么这个框架没有经过深思熟虑。

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

有帮助吗?

解决方案

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

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

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

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

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

    }
}

现在您需要创建一个继承自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
    }
}

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

IDrawable drawableService = new Drawable(myGraphicsDeviceManager);

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

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

其他提示

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

在博客文章中,我建议静态接口只能在泛型类型约束中使用 - 但可能非常方便,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)
    {
    }
}

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

interface IPerson
{
    IPerson(string name);
}

interface ICustomer
{
    ICustomer(DateTime registrationDate);
}

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

按照惯例,“接口构造函数”的实现是按照惯例进行的。由类型名称替换。

现在创建一个实例:

ICustomer a = new Person("Ernie");

我们是否会遵守合同 ICustomer

那是怎么回事:

interface ICustomer
{
    ICustomer(string address);
}

你不能。

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

如果你有一些需要初始化的状态,你应该考虑使用抽象基类。

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

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
}

另一方面,如果你想测试一个类型是否具有无参数构造函数,你可以使用反射来做到这一点:

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;
  }
}

希望这有帮助。

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

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

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)。所以在IQueueItem接口上我有 -

public interface IQueueItem
{
    CloudQueueMessage ToMessage();
}

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

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

现在我终于可以在没有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;
    }
}

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

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

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

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

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

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
}

可能的用法示例:

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

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

解决此问题的一种方法是利用泛型和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);
    } 
}

现在使用它时:

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
}

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

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);
    } 
}

使用它:

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

() => new Triangle(manager) 

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

你可以用泛型技巧做到这一点,但它仍然容易受到Jon Skeet所写的影响:

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

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

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

接口的目的是强制执行特定的对象签名。它不应该明确地关心对象内部如何工作。因此,从概念的角度来看,接口中的构造函数实际上没有意义。

但还有一些替代方案:

  • 创建一个充当最小默认实现的抽象类。该课程应具有您期望实施类具有的构造函数。

  • 如果您不介意过度杀伤,请使用AbstrackFactory模式,并在工厂类接口中声明具有所需签名的方法。

  • 通过 GraphicsDeviceManager 作为参数 UpdateDraw 方法。

  • 使用组合面向对象编程框架来传递 GraphicsDeviceManager 进入对象需要它的部分。在我看来,这是一个相当实验性的解决方案。

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

你没有。

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

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

鉴于接口是必须以指定方式使用的合同。对于某些情况,以下方法可能是一种可行的替代方法:

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;
    }

}

强制某种构造函数的一种方法是在接口中仅声明 Getters ,这可能意味着实现类必须有一个方法,理想情况下是构造函数,才能设置值(<代码>私人 ly)。

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

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

我使用以下模式使其防弹。

  • 从基类派生类的开发人员不会意外创建公共可访问的构造函数
  • 最终类开发人员被迫经历公共创建方法
  • 一切都是类型安全的,不需要强制转换
  • 它具有100%的灵活性,可以在任何地方重复使用,您可以在其中定义自己的基类。
  • 尝试一下,您不能不对基类进行修改而破坏它(除非您定义了一个过时的标志,而没有错误的标志设置为true,但即使这样,您最终会出现警告)

        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);
    }
    

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

        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());
    }

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

将SetGraphicsDeviceManager(GraphicsDeviceManager gdo)添加到接口,这样实现类将被强制编写一个需要从构造函数调用的逻辑。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top