【问题标题】:Override a Property with a Derived Type and Same Name C#使用派生类型和同名 C# 覆盖属性
【发布时间】:2010-08-23 19:32:42
【问题描述】:

我正在尝试使用不同但具有相同名称的派生类型覆盖基类中的属性。我认为它可能通过协方差或泛型实现,但不知道该怎么做?

以下代码得到错误:

错误 1 ​​'Sun.Cache':类型必须是 'OuterSpace.Cache' 匹配被覆盖 成员'OuterSpace.Cache'

public class OuterSpace {
    public virtual OuterSpaceData Data {get; set;}
    public virtual OuterSpaceAnalysis Analysis {get; set;}
    public virtual OuterSpaceCache Cache {get; set;}


    public class OuterSpaceData {
        //Lots of basic Data Extraction routines eg
        public virtual GetData();
    }
    public class OuterSpaceAnalysis {
        //Lots of Generic Analysis on Data routines eg
        public virtual GetMean();
    }
    public class OuterSpaceCache {
        //Lots of Caches of Past Analysis Results:
        public Dictionary<AnalysisType, List<Result>> ResultCache;
    }
}

public class Sun : OuterSpace {
    public override SunData Data {get; set;}
    public override SunAnalysis Analysis {get; set;}
    public override SunCache Cache {get; set;}

    public SunData : OuterSpaceData {
        //Routines to specific get data from the sun eg
        public override GetData();
    }

    public SunAnalysis : OuterSpaceAnalysis {
        //Routines specific to analyse the sun: eg
        public double ReadTemperature();
    }
    public SunCache : OuterSpaceCache {
        //Any data cache's specific to Sun's Analysis
        public Dictionary<AnalysisType, List<Result>> TempCache;
    }
}

public class Moon : OuterSpace {} etc.

对于最终结果,当我处理 Sun 的“数据”对象时,我不希望有两个数据对象(继承和基类),但是当我尝试覆盖该属性时,它需要 Sun 变量与基类相同的类型。例如:

Sun EarthSun = new Sun()
EarthSun.Analyse()  //The OuterSpace Analysis Saves results to Sun Cache:

//Now try use the result:
EarthSun.Cache[0]...

与此非常相似,但使用派生类型而不是字符串数组: C# Member variable overrides used by base class method

这个答案对我来说没有多大意义: How to override member of base class after inheritance in C++

或者这意味着它不可能? Can I Override with derived types?

帮助! :) 有什么解决办法吗?

【问题讨论】:

  • 有点跑题了,但是说太阳是一种外太空有意义吗?这里的一些尴尬可能来自使用继承,而更简单的结构就足够了。
  • 我欢迎建议?这是问题的demo伪代码,实际项目很大,只是在为未来做打算。
  • 仅从您提供的示例结构很难说。要考虑的主要事情是您实际上是如何使用这些类的。特别是 Cache、Analysis 和 Data 类的什么使得将它们作为基类访问是有益的?您有一个协调分析的类,但似乎每种分析的协调也不同;这向我暗示了可能需要进一步调查的隐藏责任。如果您还没有通用结构的用例,我会避免使用它;雅尼。
  • 嘿:我有 30-40 种复杂算法,它们访问标准化的“OuterSpace”类 - 其中时间序列数据根据基数(DateTime-Value)存储。但是对于例如太阳类型(那里有许多不同的太阳);你还有额外的数据+额外的算法。 90% 的分析可以使用基类算法(访问基类存储变量)完成,但 10% 的 Sun 算法位于 SunAnalysis 类中。通过简单地继承基类,我可以轻松访问他们的算法 + 数据。
  • 听起来你肯定需要可扩展性,所以这实际上只是它的结构问题。我个人的第一印象是继承和关联的耦合有点难以理解,可能有更好的方法来拆分事物以使组件彼此隔离。您可能会问的一个问题是,您是否可以独立于逻辑的其余部分来测试逻辑的单个部分(例如“额外”逻辑)。这可能会引导您到自然的地方来分解事物并减少耦合。

标签: c# inheritance overriding covariance


【解决方案1】:

这不可能完全按照你想要的方式。

您可以创建一个具有相同名称的“新”属性:

public new SunData Data
{
  get { return (SunData) base.Data; }
  set { base.Data = value; }
}

这并不完全相同,但它可能与您将要得到的一样接近。

另一种可能的方法是:

public SunData SunData { get; set; }

public override OuterSpaceData Data
{
  get { return SunData; }
  set { SunData = (SunData)value; }
}

这种方法的优点是它确实保证不可能在您的 Data 属性中放入不是 SunData 的东西。缺点是您的 Data 属性不是强类型的,如果您希望将其静态类型为 SunData,则必须使用 SunData 属性。

有一种丑陋的方式来获得“两全其美”,但代价是代码混淆并且之后难以理解 - 通过引入一个中间派生类,该类对基类 Data 属性和重定向到具有新名称的不同受保护属性,然后让最终派生类添加一个“新”数据属性,然后调用中间属性。不过,这确实是一个丑陋的 hack,即使我已经这样做了,我也不推荐它。

【讨论】:

  • 您可能已经知道,当对象被转换为基类时,“新”属性方法可能会产生不良副作用。
  • 谢谢,我不太明白:这是否解决了 SunCache 为 Null 的问题,因为 Analysis 正在将结果保存到基类? SunCache 中的额外变量呢?他们会迷路吗?
  • 另外,请注意,在您的第二种方法中,数据是强类型的;我们只是缺少对属性调用的编译时类型检查。
  • @kbrimington - 实际上,当对象被转换为基类时,我实现“新”属性方法的方式是安全的,因为它仍然使用底层“数据”属性作为其存储。 @JaredBroad:我不确定我是否理解这个问题——保存的对象是一个带有所有额外字段和属性的 SunData 对象。除非您通过将其替换为“new OuterSpaceWhatever()”来主动丢弃这些额外信息,否则您会没事的——第二种方法可以防止这种情况发生。如果这是 C++ 代码,可能会因为“切片”而丢失数据,但在 C# 中不会。
  • @Stuart 很抱歉,“新”实现并不“安全”。取这段代码 OuterSpace sun = new Sun(); //这将编译,因为它使用基类属性 sun.Data = new OuterSpaceData(); //这将编译,因为它使用了 Suns 属性 //然而它会导致运行时转换错误 SunData data = ((Sun)sun).Data;
【解决方案2】:

尝试使用泛型。以下类为OuterSpace 提供了一个通用基类,其参数约束对属性类型强制继承规则。为了清楚起见,我省略了小类型中的方法。

public class OuterSpace<TData, TAnalysis, TCache>
    where TData : OuterSpaceData
    where TAnalysis : OuterSpaceAnalysis
    where TCache : OuterSpaceCache
{
    public virtual TData Data { get; set; }
    public virtual TAnalysis Analysis { get; set; }
    public virtual TCache Cache { get; set; }
}
public class OuterSpaceData { }
public class OuterSpaceAnalysis { }
public class OuterSpaceCache { }

public class Sun : OuterSpace<SunData, SunAnalysis, SunCache>
{
    public override SunData Data { get; set; }
    public override SunAnalysis Analysis { get; set; }
    public override SunCache Cache { get; set; }
}
public class SunData : OuterSpaceData { }
public class SunAnalysis : OuterSpaceAnalysis { }
public class SunCache : OuterSpaceCache { }

这种方法的吸引力在于它允许您返回强类型属性对这些属性的类型强制执行基本继承约束。这些方法是完全可覆盖的;但是,请注意,可能需要重写以避免基类实现的转换问题。

【讨论】:

  • 这看起来很漂亮,也许它会起作用,但我尝试实现它并且很快就变得一团糟。该程序实际上是 50-100k 长,不得不重新输入每个“外太空”让我有点害怕 :) 谢谢,
  • @Jared:感谢您的回复。它很漂亮,而且确实有效;但是,您可能会发现使用@stuart 的方法(第二种方法——尽可能避免new 成员声明)并在必要时使用类型转换来获得专门的方法就足够了。 (例如var data = mySunDataObject.Data as SunData;)。
  • @Jared:还要注意,Sun 类中的三个覆盖只有在您打算在 getter 和 setter 上具有不同的行为时才真正需要。对于我在示例中使用的普通的空 getter 和 setter,您可以删除覆盖并具有相同的效果。
  • 谢谢,它们原本只是简单的变量,但在某个地方读到了不能覆盖成员变量的地方?
  • @Jared:没错。尽管有些人认为空的 getter-setter 属性和公共字段之间没有太大区别,但至少属性可以是虚拟的/可覆盖的。如果基类无法知道属性(或字段)的类型,泛型提供了灵活性,并提供了卓越的编译时类型检查;但是,正如您所观察到的,维护复杂的泛型类型结构可能具有挑战性。想象一个名为MyBaseClass&lt;TProp1, TProp2, TProp3, TProp4,...&gt; 的类型显然,过度抽象的类是不可取的。很高兴知道这是可能的。
【解决方案3】:

如果您正在寻找协方差路线,这样的方法应该可以:

public class OuterSpace<TCacheType> where TCacheType : OuterSpaceCache {
    public virtual OuterSpaceData Data {get; set;}
    public virtual OuterSpaceAnalysis Analysis {get; set;}
    public virtual TCacheType Cache {get; set;}


    public class OuterSpaceData {
        //Lots of basic Data Extraction routines eg
        public virtual GetData();
    }
    public class OuterSpaceAnalysis {
        //Lots of Generic Analysis on Data routines eg
        public virtual GetMean();
    }
    public class OuterSpaceCache {
        //Lots of Caches of Past Analysis Results:
        public Dictionary<AnalysisType, List<Result>> ResultCache;
    }
}

public class Sun : OuterSpace<SunCache> {
    public override SunData Data {get; set;}
    public override SunAnalysis Analysis {get; set;}

    public SunData : OuterSpaceData {
        //Routines to specific get data from the sun eg
        public override GetData();
    }

    public SunAnalysis : OuterSpaceAnalysis {
        //Routines specific to analyse the sun: eg
        public double ReadTemperature();
    }
    public SunCache : OuterSpaceCache {
        //Any data cache's specific to Sun's Analysis
        public Dictionary<AnalysisType, List<Result>> TempCache;
    }
}

公平警告,完全未编译的代码,我可能完全错了 :) 但这是我认为你想要的东西......

哦,我认为这是一个仅适用于 .Net 4.0 的解决方案,我不认为您可以更早地做到这一点,那里引入了协方差...

【讨论】:

    【解决方案4】:

    如果 SunData 与 Outerspace Data 的赋值兼容(继承自),则无需修改 OuterSpaceData 的类型

    【讨论】:

    • 我需要访问 SunCache / SunAnalysis 特定的变量/方法(最好不必在每次需要使用它们时进行转换)。这不是说需要正确设置类型吗?
    • @JaredBroad 你说的是你有设计的味道。您确定“从不”(非常罕见)必须降级。看来您正在为功能派生看看策略模式并将您的通用逻辑/功能放在单独的类中并注入这些类型的对象
    • 感谢建议,可以看到接口也有帮助,以后有时间再玩。 en.wikipedia.org/wiki/Strategy_pattern#C.23
    【解决方案5】:

    不确定泛型,但您可以通过简单的多态实现有限的效果(假设 SunData 继承自 Data 等)

    public class Sun : OuterSpace 
    {
      void SomeInitializationMethod()
      {
        Data = new SunData();
        Analysis = new SunAnalysis(); // etc
      }
    
        }
    
    // You could also make casting simpler with a generic method on the base
    public T GetData<T>()
    {
      if (Data is T)
      {
        return Data as T;
      }
      return null;
    };
    

    【讨论】:

    • 谢谢,最初有类似的东西,但希望尽可能避免在代码中乱扔演员表。至少选择的答案是集中的.. :\
    猜你喜欢
    • 2017-07-05
    • 1970-01-01
    • 2021-08-31
    • 1970-01-01
    • 1970-01-01
    • 2018-07-31
    • 1970-01-01
    • 1970-01-01
    • 2012-06-02
    相关资源
    最近更新 更多