【问题标题】:C# access children's static member in parentC#在父级中访问子级的静态成员
【发布时间】:2017-08-20 08:39:29
【问题描述】:

我有一个类 Foo,它是许多其他类(如 Bar 和 Baz)的基类,我想使用 Bar 和 Baz 中的静态成员在 Foo 中进行一些计算,如下所示:

public class Foo{
    public result1 {
         get{
             return field1;
         }
    }
}

public class Bar : Foo{
    public const int field1 = 5;
}

public class Baz : Foo{
    public const int field1 = 10;
}

我能想到的唯一解决方案是将所有字段包装在一个容器中,为每个对象添加一个额外的标识符,并使用函数返回字段,就像这样

Bar : Foo{
    public readonly int id = 0;
    public static Wrapper wrapper;
}

public Wrapper GetWrapper(int id){
    switch(id){
        case 0:
            return Bar.wrapper;
    }
}

但是,如您所见,我需要维护一个额外的类和函数,并且我不想让我的代码碎片化。有没有其他选择?

【问题讨论】:

  • 您可以添加一个虚拟(或更好的抽象)函数而不是静态成员,它将返回子类中的值...
  • 是的,没有办法绕过像虚拟成员(getter/get 方法)这样的东西——类型系统不允许你正在寻找的这种静态继承
  • 静态和继承不能很好地结合在一起。如果你试图让他们一起玩,你最终会导致两者中的一个打败另一个。

标签: c# inheritance static-members


【解决方案1】:

编辑

您所要求的,即从基类访问子类中的 static 或 const 值在技术上是可行的,但这样做会违反良好的 SOLID OO 设计原则。此外,由于您需要一个特定子类的实例,以便能够“推理”子类的类型以获得适当的field1,因此静态处理这个问题几乎没有意义。

相反,这里常用的、更简洁的方法是使用subtype polymorphicism,这将允许基类中的调用方法或外部类中的方法根据子类。这允许控制返回的值以保留在适当的子类中(即,按照您的说法,代码不会变得“碎片化”)。

使用子类多态性的替代解决方案(推荐)

子类多态方法(即使用virtual/abstract 和override 关键字)将允许您封装对每个子类可定制的值(或对象)的检索。在这里,抽象在概念上仍然是“给我一个整数值”,然后可以从调用者抽象(隐藏)“如何”返回值的特定于子类的实现。此外,通过将基本属性标记为abstract,您将强制所有子类实现该属性,这样就不会忘记提供值的要求。

即我会推荐这样的多态方法:

public abstract class Foo
{
    public abstract int Result { get;  }
}

public class Bar : Foo
{
    // This is implementation specific. Hide it.
    private const int field1 = 5;

    public override int Result 
    { 
        get { return field1; }
    }
}

public class Baz : Foo
{
    public override int Result 
    { 
        // No need for this implementation to be a constant ...
        get { return TheResultOfAReallyComplexCalculationHere(); }
    }
}

如果基类Foo 上没有其他可重用的具体方法,那么您也可以将抽象建模为接口,效果相同:

public interface IFoo
{
    int Result { get;  }
}

在没有多态性的情况下解决这个问题(不推荐)

任何在编译时访问子类上的静态字段的尝试通常都需要某处的代码来切换(或映射)子类实例的实际类型,例如:

public class Foo
{
    public int result1 
    {
         get
         {
             switch(this.GetType().Name)
             {
                case "Bar":
                    return Bar.field1;
                case "Baz":
                    return Baz.field1;
                default:
                    return 0;
             }
         }
    }

    public void MethodRequiringValueFromSubclass()
    {
        Console.WriteLine(result1);
    }
}

public class Bar : Foo
{
    public const int field1 = 5;
}

public class Baz : Foo
{
    public const int field1 = 10;
}

这里的问题是违反了Open and Closed principal,因为每次添加新的子类时,都需要更改result1 方法以适应新类。

【讨论】:

  • 你不能在非抽象类中使用抽象属性。你的意思是virtual?
  • 谢谢,添加了缺少的抽象类限定符
【解决方案2】:

我建议使用抽象函数而不是使用静态成员。

public abstract class Foo{
    public result1 {
         get{
             return get_field1();
         }
    }
    protected abstract int get_field1();
}

public class Bar : Foo{
    public const int field1 = 5;
    protected override int get_field1() { return field1;}
}

public class Baz : Foo{
    public const int field1 = 10;
    protected override int get_field1() { return field1;}
}

【讨论】:

  • 如果 OP 不希望 Foo 是抽象的怎么办?
  • @Guy .. 这只是一个建议.. 如果他不希望它是抽象的,他可以使用虚拟。
【解决方案3】:

您可以将构造函数参数添加到可以从继承者传递的 Foo 类中,因此您不需要额外的类,并且耦合度也会降低

public class Foo
{
    private readonly int _field1;
    public Foo(int field1)
    {
         _field1 = field1;
    }
}

或者您可以完全从继承类型中使用它,因为静态/常量成员是类类型的成员

public class Foo
{
    public result1 
    {
         get
         {
             return Bar.field1;
         }
    }
}

但这会给您的代码带来更少的灵活性和更多的耦合。

您还可以选择使用可以在派生类中实现并在基类中使用的虚拟属性:

public class Foo
{
    public virtual int Field { get { return 0; } }
}

【讨论】:

    【解决方案4】:

    您可以在每个子类中使用virtual 并覆盖result1,而不是将Foo 抽象为其他答案

    public class Foo
    {
        public virtual int result1 { get; }
    }
    
    public class Bar : Foo
    {
        public const int field1 = 5;
    
        public override int result1
        {
            get { return field1; }
        }
    }
    
    public class Baz : Foo
    {
        public const int field1 = 10;
    
        public override int result1
        {
            get { return field1; }
        }
    }
    

    如果您希望默认 result1 返回 0 以外的值,您可以给它另一个值

    public class Foo
    {
        public virtual int result1 { get; } = -1;
    }
    

    【讨论】:

    • 附带条件是所有其他不覆盖 result1 的 Foo 子类将返回 0。
    • 其实虚拟的getter属性必须有body或者setter。如果您不需要 setter 和 body,则需要将其标记为抽象。
    • @StuartLC 如果将其标记为抽象,则所有子方法都必须实现 result1 才能返回一些值。我添加了一个选项以将其编辑到我的答案中。
    • @hazevich 不正确。我的答案中的代码是有效的。
    • @Guy 使用 .NET 4.5 或更低版本在 C# 5 上尝试您的代码。并非每个人都使用甚至能够使用 C# 6 功能。
    【解决方案5】:

    当我回答自己的问题时,我总是觉得自己像个混蛋……但没有看到我的期望,所以我不妨分享一下我在经历了一夜令人难以置信的事情后得到的结果。

    我不想使计算抽象/虚拟的原因是因为有很多子类,并且所有子类的公式都是相同的。我只是拒绝重复输入相同的代码 10-20 次。

    也不能使静态字段成为非静态字段,因为它们应该可以在类级别访问,而且它们可以变大,并且它们对于所有实例都是相同的。

    我能想出的唯一能最小化代码片段的解决方案是这样的

    public class Foo {
        public class Wrapper {
            Fields...
        }
        public Wrapper wrapper; // reference
        public int result1 { get; }
    }
    
    public class Bar : Foo {
        public static Wrapper subclassWrapper; // put in the implementation
        public Bar() : base(){
            wrapper = subclassWrapper;
        }
    }
    

    所以现在每个实例都需要保存一个额外的引用,但是我不需要保存一个函数。包装器保留在基类中,因此碎片较少。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-13
      • 1970-01-01
      • 2018-09-15
      • 1970-01-01
      • 2017-06-09
      • 2014-08-22
      • 2021-11-05
      • 1970-01-01
      相关资源
      最近更新 更多