Override Property with different compatible Type

34,316

Solution 1

Here's an alternative approach to proposed solution:

public abstract class Base
{
    public abstract void Use();
    public abstract object GetProp();
}

public abstract class GenericBase<T> : Base
{
    public T Prop { get; set; }

    public override object GetProp()
    {
        return Prop;
    }
}

public class StrBase : GenericBase<string>
{
    public override void Use()
    {
        Console.WriteLine("Using string: {0}", Prop);
    }
}

public class IntBase : GenericBase<int>
{
    public override void Use()
    {
        Console.WriteLine("Using int: {0}", Prop);
    }
}

Basically I've added a generic class in the middle that stores your properly-typed property. this will work assuming that you never need to access Prop from the code that iterates the members of the List<Base>. (You could always add an abstract method to Base called GetProp that casts the generic to an object if that's required.)

Sample usage:

class Program
{
    static void Main(string[] args)
    {
        List<Base> l = new List<Base>();

        l.Add(new StrBase {Prop = "foo"});
        l.Add(new IntBase {Prop = 42});

        Console.WriteLine("Using each item");
        foreach (var o in l)
        {
            o.Use();
        }
        Console.WriteLine("Done");
        Console.ReadKey();
    }
}

Edit: Added the GetProp() method to illustrate how the property can be directly accessed from the base class.

Solution 2

You can't override the type of a property. Take a look at the following code:

StrBase s = new StrBase();
Base b = s;

This is completely valid code. But what happens when you try to do this?

b.prop = 5;

The integer can be converted to object, because everything is derived from object. But since b is actually a StrBase instance, it would have to convert the integer to a string somehow, which it can't. So that is why you aren't allowed to override the type.

The same principle applies to generics:

List<BaseG<object>> l = new List<BaseG<object>>();
BaseG<string> s = new BaseG<string>();

// The compiler will not allow this.
l.add(s);

// Here's the same problem, convert integer to string?
BaseG<object> o = l[0];
o.prop = 5;

This is because generic types in C# 2.0 are invariant. C# 4.0 does allow this type of conversions, called covariance and contravariance.

Solutions

An option is to cast the object back to string when you need it. You could add type validation in the subclass:

public class StrBase : Base
{
  private string propValue;

  public override object prop {
    get
    {
      return this.propValue;
    }
    set
    {
      if (value is string)
      {
        this.propValue = (string)value;
      }
    }
  }
}

You could also expose a type-safe property in the subclass:

public class StrBase : Base
{
  public string strProp {
    get
    {
      return (string)this.prop;
    }
    set
    {
      this.prop = value;
    }
  }
}

Solution 3

This is possible since C# 9.0

Beginning with C# 9.0, override methods support covariant return types.

(see Microsoft docs)

Share:
34,316

Related videos on Youtube

RainerM
Author by

RainerM

Updated on July 09, 2022

Comments

  • RainerM
    RainerM almost 2 years

    I need a base class with a property where I can derive classes with the same property but different (compatible) types. The base Class can be abstract.

    public class Base
    {
        public virtual object prop { get; set; }
    }
    
    public class StrBase : Base
    {
        public override string prop { get; set; } // compiler error
    }
    
    public class UseIt
    {
        public void use()
        {
            List<Base> l = new List<Base>();
            //...
        }
    }
    

    I tried it with Generics but that gives me a problem when using the class, because I want to store differently typed base classes in the List.

    public class BaseG<T>
    {
        public T prop { get; set; }
    }
    
    public class UseIt
    {
        public void use()
        {
            List<BaseG> l = new List<BaseG>(); // requires type argument
            //...
        }
    }
    
  • Lasse V. Karlsen
    Lasse V. Karlsen almost 14 years
    +1: This is a clean solution to the problem at hand. Separate out the base class from the generic part.
  • Bernoulli IT
    Bernoulli IT about 4 years
    The simplicity of the first solution is attractive. Though, what happens if "prop" gets set with a different Type value? How would you look at this now 10 later? Anything new under the sun ;)
  • Niels van der Rest
    Niels van der Rest about 4 years
    @BernoulliIT, I would probably reconsider the overall design, because I haven't really had the need for this ;) In my experience, code like this tends to come up when reading data structures that allow different value types, such as JSON. Usually it's better to just type properties as object and realize that you're sometimes dealing with data structures which are better left untyped. Trying to shoehorn it into the type system won't make it better, only more complicated.
  • Mass Dot Net
    Mass Dot Net over 3 years
    How would you solve OP's problem using a covariant/contravariant approach?
  • Axel Samyn
    Axel Samyn about 2 years
    Here's the link to co/contra-variance explanations