Переопределить свойство с другим совместимым типом - PullRequest
17 голосов
/ 29 июня 2010

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

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, но это вызывает у меня проблемы при использовании класса, потому что я хочу хранить базовые классы с различными типами в списке.

public class BaseG<T>
{
    public T prop { get; set; }
}

public class UseIt
{
    public void use()
    {
        List<BaseG> l = new List<BaseG>(); // requires type argument
        //...
    }
}

Ответы [ 2 ]

26 голосов
/ 29 июня 2010

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

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 (), чтобы проиллюстрировать, как напрямую можно получить доступ к свойству из базового класса.

7 голосов
/ 29 июня 2010

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

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's the same problem, convert integer to string?
BaseG<object> o = l[0];
o.prop = 5;

Это потому, что универсальные типы в C # 2.0 являются инвариантными.C # 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;
    }
  }
}
...