views:

189

answers:

5

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: http://stackoverflow.com/questions/1112458/c-member-variable-overrides-used-by-base-class-method

And this answer didn't make much sense to me: http://stackoverflow.com/questions/1123975/how-to-override-member-of-base-class-after-inheritance-in-c

Or perhaps this means its just not possible? http://stackoverflow.com/questions/157119/c-can-i-override-with-derived-types

Help! :) Any work around?

A: 

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
A: 

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

Conrad Frix
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
@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
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
+2  A: 

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
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
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
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
@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
@Stuart I'm sorry but the "new" implementation is not "safe". Take this codeOuterSpace sun = new Sun();//this will compile because it uses the base class propertysun.Data = new OuterSpaceData();//this will compile because it uses the Suns property//How ever it will result in a run time cast errorSunData data = ((Sun)sun).Data;
Rune FS
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
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
A: 

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;
};
nonnb
Thanks, had something similar initially but want to avoid casts littered through code where possible. At least with selected answer casting is centralised.. :\
JaredBroad
+1  A: 

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
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
@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
@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
Thanks, they were originally simply variables but read somewhere that you can't override member variables?
JaredBroad
@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<TProp1, TProp2, TProp3, TProp4,...>` Obviously, overly-abstracted classes are undesirable. It's good to know it's possible.
kbrimington