Мне нужен базовый класс со свойством, где я могу получить классы с тем же свойством, но различными (совместимыми) типами. Базовый класс может быть абстрактным.
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>();
//...
}
}
Я попробовал его Дженериками, но это дает мне проблему при использовании класса, потому что я хочу сохранить по-другому введенные базовые классы в Списке.
public class BaseG<T>
{
public T prop { get; set; }
}
public class UseIt
{
public void use()
{
List<BaseG> l = new List<BaseG>(); // requires type argument
//...
}
}
Вот альтернативный подход к предлагаемому решению:
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
под названием 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();
}
}
Edit : Добавлен метод GetProp (), чтобы проиллюстрировать, как к свойству можно получить прямой доступ из базового класса.
Вы не можете переопределить тип свойства. Взгляните на следующий код:
StrBase s = new StrBase();
Base b = s;
Это полностью допустимый код. Но что произойдет, если вы попытаетесь это сделать?
b.prop = 5;
Целое число можно преобразовать в объект
, потому что все происходит от объекта
. Но поскольку 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 допускает такой тип преобразований, называемый ковариацией и контравариантностью.
Решения
Вы можете преобразовать объект
обратно в строку, когда вам это нужно. Вы можете добавить проверку типа в подкласс:
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;
}
}
}