Переопределить свойство с другим совместимым типом

Мне нужен базовый класс с свойством, в котором я могу получить классы с тем же свойством, но с разными (совместимыми) типами. Базовый класс может быть абстрактным.

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>();
        //...
    }
}

Я попробовал его с помощью Generics, но это создает проблемы при использовании класса, потому что я хочу хранить в List 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
        //...
    }
}

Ответы

Ответ 1

Здесь альтернативный подход к предлагаемому решению:

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

В основном я добавил общий класс в середине, который хранит ваше правильно типизированное свойство. это будет работать при условии, что вам никогда не потребуется доступ к Prop из кода, который выполняет итерации членов List<Base>. (Вы всегда можете добавить абстрактный метод к Base, называемый GetProp, который генерирует общий объект для объекта, если это необходимо.)

Использование образца:

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

Изменить: добавлен метод GetProp(), чтобы проиллюстрировать, как свойство можно напрямую получить из базового класса.

Ответ 2

Вы не можете переопределить тип свойства. Взгляните на следующий код:

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

Это вполне допустимый код. Но что происходит, когда вы пытаетесь это сделать?

b.prop = 5;

Целое число можно преобразовать в object, потому что все получено из object. Но так как b на самом деле является экземпляром StrBase, он должен каким-то образом преобразовать целое число в строку, чего не может. Поэтому вы не можете переопределить тип.

Тот же принцип применяется к дженерикам:

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

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

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

Это связано с тем, что общие типы в С# 2.0 являются инвариантными. С# 4.0 разрешает этот тип преобразований, называемый ковариацией и контравариантностью.

Решения

Опция заключается в том, чтобы вернуть object обратно в строку, когда вам это нужно. Вы можете добавить проверку типов в подкласс:

public class StrBase : Base
{
  private string propValue;

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

В подклассе также можно указать свойство типа безопасного типа:

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

Ответ 3

public class first
        {
            private int someV;

            public virtual object SomeV { get => someV; set => someV = (int)value; }

            public first() { }
        }

        public class two : first
        {
            private string someV;

            public override object SomeV { get => someV; set => someV = value.ToString(); }

            public two() { }
        }

и их использование:

first firstClass = new first();
firstClass.SomeV = 1;

two twoClass = new two();
twoClass.SomeV = "abcd";

Ответ 4

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

Я написал полную статью на эту тему прямо здесь:   http://materiagame.com/2019/09/04/override-property-type-in-a-derived-class

TheUncas21