13

I'm trying to override a property in a base class with a different, but derived type with the same name. I think its possible by covarience or generics but am not sure how to do it?

The following code gets the error:

Error 1 'Sun.Cache': type must be 'OuterSpace.Cache' to match overridden member '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.

For the end result, when I address the "Data" Object of Sun I don't want there to be two Data Objects (Inherited & Base Class) but when I try override the property it requires the Sun variables to be the same type as base class. Eg:

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

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

Very similar to this but with derived type instead of string-array: C# Member variable overrides used by base class method

And this answer didn't make much sense to me: How to override member of base class after inheritance in C++

Or perhaps this means its just not possible? Can I Override with derived types?

Help! :) Any work around?

Community
  • 1
  • 1
JaredBroad
  • 660
  • 1
  • 8
  • 17
  • 1
    Slightly off-topic, but does it make sense to say that Sun is a kind of OuterSpace? Some of the awkwardness here may be from using inheritance where a simpler structure would suffice. – Dan Bryant Aug 23 '10 at 20:44
  • I welcome suggestions? This is a demo pseudo code of the problem, the actual project is very large and I'm just trying to plan for the future. – JaredBroad Aug 23 '10 at 20:57
  • it's difficult to say from just the sample structure you provided. The main thing to consider is how you're actually _using_ the classes. In particular, what is it about the Cache, Analysis and Data classes that makes it beneficial to access them as base classes? You have a class that coordinates the analysis, but it also seems that the coordination is different for each type of analysis; that suggests to me that are hidden responsibilities that may need further investigation. If you don't have a use-case yet for the generic structure, I would avoid it; YAGNI. – Dan Bryant Aug 23 '10 at 22:39
  • Hey: I have 30-40 complex algorithms, which access a standardised "OuterSpace" class - where the timeseries data is stored according to base (DateTime-Value). But for E.g. the Sun Type (many different suns out there); you also have additional data + additional algorithms. 90% of the analysis can be done using the base class algorithms (accessing the base class storage variables) but the 10% of Sun-Algorithms are located in the SunAnalysis Class. By simply inheriting the base classes I get easy access to their algos + data. – JaredBroad Aug 23 '10 at 23:00
  • it sounds like you definitely need the extensibility, so it's really just a matter of how it's structured. My personal first impression is that the inheritance and associated coupling is a bit hard to follow and there might be a better way to split things up to insulate the components from each-other. A question you might ask is whether you can test an individual portion of the logic (such as the 'extra' logic) independently from the rest of the logic. This may lead you to natural places to break things down and reduce coupling. – Dan Bryant Aug 24 '10 at 15:18

5 Answers5

14

It is not possible the way you want it, exactly.

You can create a "new" property with the same name:

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

It's not quite the same thing, but it's probably as close as you're going to get.

Another possible approach would be:

public SunData SunData { get; set; }

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

The advantage of this approach is that it does guarantee that it's impossible to put something in your Data property that is not a SunData. The downside is that your Data property is not strongly typed and you have to use the SunData property if you want it statically typed to SunData.

There's an ugly way to get the "best of both worlds" at the expense of being confusing to code and difficult to understand afterwards - by introducing an intermediate derived class that does a 'sealed override' on the base class Data property and redirects to a different protected property with a new name, and then have the ultimate derived class add a 'new' Data property that then calls the intermediate property. It really is an ugly hack, though, and even though I've done it, I wouldn't recommend it.

Stuart
  • 1,868
  • 2
  • 14
  • 14
  • 1
    As you are probably already aware, the "new" property approach can have undesirable side effects when the object is cast to a base class. – kbrimington Aug 23 '10 at 19:54
  • 1
    Thanks, I can't quite see: Does this solve the issue of the SunCache being Null because Analysis is saving results to Base class? What about extra variables in the SunCache? Will they be lost? – JaredBroad Aug 23 '10 at 19:55
  • Also, note that in your second approach, the data is strongly typed; we simply lack compile-time type checking on calls to the property. – kbrimington Aug 23 '10 at 19:58
  • @kbrimington - actually, the way I implemented the "new" property approach was safe when the object is cast to a base class, because it still uses the underlying "Data" property as its storage. @JaredBroad: I'm not sure I understand the question - the object that's saved IS a SunData object with all the extra fields and properties of it. Unless you actively discard that extra information by replacing it with a "new OuterSpaceWhatever()" you'll be fine - and the second approach protects against that. If this were C++ code, it's possible to lose data that way because of "slicing" but not in C#. – Stuart Aug 23 '10 at 20:05
  • @Stuart I'm sorry but the "new" implementation is not "safe". Take this code OuterSpace sun = new Sun(); //this will compile because it uses the base class property sun.Data = new OuterSpaceData(); //this will compile because it uses the Suns property //How ever it will result in a run time cast error SunData data = ((Sun)sun).Data; – Rune FS Aug 23 '10 at 21:00
  • Right - I meant it was "safe" in terms of ensuring that the value in the property will always actually BE a SunData. It's impossible to write this code in a way that's compile-time-typesafe without removing the "Data" property from OuterSpace entirely, or giving it only a getter and not a setter (which might be a good idea, actually). But the runtime cast error protects you from being able to actually get away with putting an illegal object in there. – Stuart Aug 23 '10 at 21:27
  • OK! Cheers tested both methods, first method by far easier, and can do without modifications to code - and the second method required me to change lots of code to use "SunData" instead of "Data" which isn't really acceptable. -> So will go have to use first and will be careful about initialisation! Thank you for help, any last ideas given additional question-comments above with @Dan? – JaredBroad Aug 23 '10 at 23:13
10

Try going about it with generics. The following class provides a generic base class for OuterSpace with constraints on its parameters that enforce inheritance rules on the property types. I've omitted methods in the small types for clarity.

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

The appeal of this approach is that it permits you to return strongly typed properties and enforce a basic inheritance constraint on the types of those properties. The methods are fully overridable; however, be advised that overriding may well be required to avoid casting issues with the base class implementation.

kbrimington
  • 25,142
  • 5
  • 62
  • 74
  • This looks pretty, and maybe it would work but I tried to implement and it got messy very fast. The program is actually 50-100k long and having to re-type every "OuterSpace" scared me a little :) Thanks though, – JaredBroad Aug 23 '10 at 20:23
  • @Jared: Thanks for your response. It is pretty, and does work; however, you may find it adequate to use @stuart's approach (the 2nd one -- avoid `new` member declarations if at all possible) and use type casting when necessary to get at specialized methods. (e.g. `var data = mySunDataObject.Data as SunData;`). – kbrimington Aug 23 '10 at 21:55
  • @Jared: Note also that the three overrides in the `Sun` class are only really necessary if you intend to have different behavior on the getter and setter. For plain-ol' empty getters and setters as I have in the example, you could remove the overrides and have the same effect. – kbrimington Aug 23 '10 at 21:57
  • Thanks, they were originally simply variables but read somewhere that you can't override member variables? – JaredBroad Aug 23 '10 at 22:44
  • @Jared: That is correct. Though some folks don't see much difference between empty getter-setter properties and public fields, at least properties can be virtual/overridable. Generics offer flexibility if the type of the property (or field, for that matter) cannot be known by the base class, and provide superior compile-time type checking; however, as you have observed, maintaining a complex generic type structure can be challenging. Imagine a type called `MyBaseClass` Obviously, overly-abstracted classes are undesirable. It's good to know it's possible. – kbrimington Aug 23 '10 at 23:38
1

If you're looking for the covariance route, something like this should work:

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

Fair warning, totally uncompiled code and I could be totally wrong :) But it's a stab at what I think you want...

Oh, and I think this is a .Net 4.0 only solution, I don't think you can do this earlier, co-variance was introduced there...

CubanX
  • 5,176
  • 2
  • 29
  • 44
0

If SunData is assignment compatible with Outerspace Data (inherits from) then you don't need to modify the type of of OuterSpaceData

Conrad Frix
  • 51,984
  • 12
  • 96
  • 155
  • I need access to SunCache / SunAnalysis specific variables/methods (preferably without having to cast each time I need to use them). Doesn't this mean the type needs to be set correctly? – JaredBroad Aug 23 '10 at 19:46
  • @JaredBroad what you are saying is that you have a design smell. You sure "never" (as in very very rarely) have to down cast. It seems you are Deriving for functionality take a look at the strategy pattern and place your common logic/functionality in seprate classes and inject objects of those types – Rune FS Aug 23 '10 at 21:02
  • Thanks for suggestion, can see interfaces helping as well, will play around later when get time. http://en.wikipedia.org/wiki/Strategy_pattern#C.23 – JaredBroad Aug 23 '10 at 22:23
0

Not sure about Generics, but you could achieve a limited effect with simple Polymorphism (assuming SunData inherits from Data etc)

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;
};
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • Thanks, had something similar initially but want to avoid casts littered through code where possible. At least with selected answer casting is centralised.. :\ – JaredBroad Aug 23 '10 at 23:16