C#中的接口与多重继承的区别

12
我有一个Class A和Class B的集合,它们都有一些属性。还有另一个Class C,它有自己的属性。
每当我创建Class C的实例,我都想用objClassC访问所有三个类的属性。
如何在C#中实现这一点?
我面临两个问题:
1.我不能在Class C中继承Class A,B(C#不支持多重继承); 2.如果我使用接口代替Class A,B(在接口中我们不能包含字段)。

3
你可以使用组合。还有mixins - Niklas B.
7个回答

29

为什么不把A类和B类的实例放在C类中呢?可以使用组合

class C
{
//class C properties
public A objA{get;set;}
public B objeB{get;set;}
}

然后你就可以访问

C objc = new C();
objc.objA.Property1 = "something";
objc.objB.Property1 = "something from b";

请查看文章"组合与继承"

编辑:

如果我使用接口而不是类A、B,(在接口中我们不能包含字段)

好的,接口无法包含字段,如果你定义了一个字段,编译器会报错。但是接口可以包含属性,唯一的例外是你无法指定访问修饰符,因为接口的所有元素都被视为public。你可以为接口'A'和'B'定义属性:

public interface IA
{
     int Property1 { get; set; }
}


public interface IB
{
    int Property2 { get; set; }
}

然后你可以在类 C 中实现它们,例如:

public class C : IA, IB
{
    public int Property1 { get; set; }
    public int Property2 { get; set; }
}

稍后你可以将它们用作:

C objC = new C();
objC.Property1 = 0;
objC.Property1 = 0;

但是如果在类C中实现IA和IB是必要的,那么需要实现IA和IB吗?我们可以只定义属性而不实现接口...? - Umar Abbas
@UmarAbbas,这样做的唯一优点是在IA或IB中持有类C或任何实现接口IAIB的其他类的对象,并具有多态行为。上面的代码只是一个例子,我还没有遇到过接口只有一堆属性而没有方法的真实场景。 - Habib
但是C类有它自己的属性。我们也可以创建C类的对象而不实现IA、IB接口。那么为什么我们要用IA、IB接口来实现C类呢? - Umar Abbas
如果我们在IA、IB中持有imp类的对象,那么它是如何实现多重继承的呢?为什么我们说它是多重继承?当我们仍然需要“多态”和“组合”时,该怎么办?谢谢回复! :) - Umar Abbas
@UmarAbbas,C#(以及我所记得的Java)中没有多重继承。上面的例子是在解释接口是否可以具有属性,并且通过实现两个接口来有点模拟多重继承。这不是纯粹的多重继承,在C#中没有办法实现多重继承。 - Habib

4

接口可以具有属性,但如果您还想使用方法,则可能需要组合或依赖注入。

Interface A
{
   int PropA {get; set;}
}


Interface B
{
  int PropB {get; set;}
}

class C : A, B
{

}

//将这些语句放在某个方法中

C c = new C();
c.PropA = 1;
c.PropB = 2;

3

接口并不是解决缺少多继承的方案。它们并不能做同样的事情。你最接近的方法就是让 C 成为 A 的子类,并具有类型为 B 的属性。 也许,如果您告诉我们 A、B 和 C 应该做什么,我们可以给出更适合您需求的答案...


2
接口可以包含属性,例如:
public interface IFoo
{
    string Bar { get; set; }
}

2
考虑使用继承和组合时,属性如何以不同的方式向客户端公开。
继承:
var myCclass = new Cclass; myClass.propertyA; myClass.propertyB; myClass.propertyC; //等等
组合:
 var myCclass = new Cclass;
    myCclass.bClass.propertyB;
    myCclass.aClass.propertyA;
    myCclass.propertyC;

继承提供了一个更清晰的API - 这是一件好事。

组合要求我了解类的内部结构 - 这并不是一件好事。这违反了德米特法则,也被称为最小知识原则。你可以通过在C类中拥有与B类和A类属性一一对应的属性来避免这种情况 - 并且你的B类和A类引用将在C类中是私有或受保护的。而C类完全控制着暴露的内容,而不是依赖于A和B不暴露你不想要的公共内容。

我同意@AlejoBrz,接口在这里并不合适。

我也认同“优先使用组合而非继承”的观点。但这只是一个指导方针,而不是硬性规定。


1
public interface IAA
{
    string NameOfA { get; set; }
}
public class AA : IAA
{
    public string NameOfA{get;set;}
}

public interface IBB
{
    string NameOfB { get; set; }
}    
public class BB : IBB
{
    public string NameOfB{get;set;}
}

public class CC : IAA, IBB
{
    private IAA a;
    private IBB b;            

    public CC()
    {
        a = new AA{ NameOfA="a"};
        b = new BB{ NameOfB="b"};
    }

    public string NameOfA{
        get{
            return this.a.NameOfA;
           }
        set{
            this.a.NameOfA = value;
           }
    }

    public string NameOfB
    {
        get{
            return this.b.NameOfB;
        }
        set{
            this.b.NameOfB = value;
        }
    }
}

1

接口不能包含字段,但它们可以包含属性。在大多数情况下,属性可以像字段一样使用,并且没有难度:

interface ISomeProperties
  {int prop1 {get;set;}; string prop2 {get; set;}}
interface IMoreProperties
  {string prop3 {get;set;}; double prop4 {get; set;}}
interface ICombinedProperties : ISomeProperties, IMoreProperties; 
  { }

给定类型为ICombinedProperties的存储位置,可以直接访问所有四个属性,而无需繁琐操作。

需要注意的是,有一些事情可以使用字段完成,但无法使用属性完成。例如,虽然可以将字段传递给Interlocked.Increment,但属性不行;尝试通过将其复制到变量中,调用Interlocked.Increment,然后将结果再次复制回属性来“增加”属性,在某些情况下可能会“成功”,但如果两个线程同时尝试做同样的事情,则会失败(例如,两个线程都可以读取值为5的值,将其增加到6,然后写回6,而两个线程调用最初等于5的字段上的Interlocked.Increment将保证产生7)。
为了解决这个问题,可能需要使接口包括一些方法,这些方法执行字段上的交错方法(例如,可以有一个函数,在字段上调用Interlocked.Increment并返回结果),和/或包括将指定委托与字段作为ref参数调用的函数(例如,
delegate void ActionByRef<T1>(ref T1 p1);
delegate void ActionByRef<T1,T2>(ref T1 p1, ref T2 p2);
delegate void ActionByRef<T1,T2,T3>(ref T1 p1, ref T2 p2, ref T3 p3);
interface IThing
{ // 必须允许客户端代码直接使用类型为 T 的字段。
  void ActOnThing(ActionByRef<T> proc);
  void ActOnThing<ExtraT1>(ActionByRef<T, ExtraT1> proc, ref ExtraT1 ExtraP1);
  void ActOnThing<ExtraT1, ExtraT2>
       (ActionByRef<T> proc, ref ExtraT1 ExtraP1, ref ExtraT2 ExtraP2);
}

给定接口的实例,可以执行以下操作:

  theInstance.ActOnThing(
    (ref int param) => Threading.Interlocked.Increment(ref param)
  );

或者,如果有本地变量 maskValuexorValue,并且想要原子更新字段为 field = (field & maskValue) ^ xorValue

theInstance.ActOnThing(
    (ref int Param, ref int MaskValue, ref int XorValue) => {
        int oldValue,newValue;
        do {oldValue = param; newValue = (oldValue & MaskValue) ^ XorValue;
        while (Threading.Interlocked.CompareExchange(ref Param, newValue, oldValue) !=
          oldValue),
    ref maskValue, ref xorValue);
  );

如果只有少数几种类型的操作需要对字段执行,最简单的方法是将它们直接包含在接口中。另一方面,上述方法允许接口以这样的方式公开其字段,以使客户端可以对其执行任意序列的操作。


@radarbob:问题的一部分与接口不能包含字段有关。其他答案承认,在许多情况下,可以使用属性代替字段。我想强调的是,即使在希望以属性无法替代的方式使用字段时,仍然可以使用接口。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接