Переопределить свойство с производным типом и тем же именем С#

Я пытаюсь переопределить свойство в базовом классе с другим, но производным типом с тем же именем. Я думаю, что это возможно с помощью ковариации или дженериков, но я не уверен, как это сделать?

Следующий код получает ошибку:

Ошибка 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 specific to Sun 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]...

Очень похоже на это, но с производным типом вместо string-array: С# Переопределения переменных-членов, используемые методом базового класса

И этот ответ не имел особого смысла для меня: Как переопределить член базового класса после наследования в C++

Или, возможно, это означает, что это просто невозможно? Можно ли переопределить с производными типами?

Помогите! :) Любая работа вокруг?

Ответ 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 базового класса и перенаправляет на другое защищенное свойство с новым именем, а затем конечный производный класс добавляет "новое" свойство 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 { }

Привлекательность этого подхода заключается в том, что он позволяет вам возвращать строго типизированные свойства и применять базовое ограничение наследования для типов этих свойств. Методы полностью переопределены; однако следует помнить, что переопределение вполне может потребоваться, чтобы избежать проблем приведения к реализации базового класса.

Ответ 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 specific to Sun Analysis
        public Dictionary<AnalysisType, List<Result>> TempCache;
    }
}

Честное предупреждение, полностью не скомпилированный код, и я могу ошибаться :) Но это удар в то, что, я думаю, вы хотите...

О, и я думаю, что это решение только для .Net 4.0, я не думаю, что вы можете сделать это раньше, была введена совместная дисперсия...

Ответ 4

Если SunData совместим по назначению с данными Outerspace (наследует от), вам не нужно изменять тип OuterSpaceData

Ответ 5

Не уверен насчет Generics, но вы можете достичь ограниченного эффекта с помощью простого полиморфизма (при условии, что 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;
};