С# Generics - Как мне вернуть определенный тип?
Возможно, я все это сделаю неправильно.
У меня есть куча классов, которые происходят из класса "Модель", базового класса с кучей общих свойств и методов. Я хочу, чтобы все они реализовали набор функций:
public abstract void Create();
public abstract T Read<T>(Guid ID); //<--Focus on this one
public abstract void Update();
public abstract void Delete();
Затем я реализую его в дочернем классе, например "Назначение":
public override T Read<T>(Guid ID)
{
var appt = db.Appointments.First(a => a.AppointmentID.Equals(ID));
var appointment = new Appointment()
{
DateEnd = appt.dateEnd.GetValueOrDefault(),
Location = appt.location,
Summary = appt.summary
};
return appointment;
}
Это генерирует исключение "Невозможно неявно преобразовать тип" Назначение "в T". Если я изменю подпись метода на "public override Appointment Read (Guid ID)", тогда компилятор говорит, что я не реализовал абстрактный метод в дочернем классе.
Что мне не хватает? Может ли кто-нибудь дать мне несколько примеров кода?
Ответы
Ответ 1
Похоже, вы могли бы использовать общий базовый класс! Рассмотрим следующее:
class Model<T>
{
public abstract T Read(Guid ID);
}
class Appointment : Model<Appointment>
{
public override Appointment Read(Guid ID) { }
}
Теперь все подклассы строго типизированы. Конечно, компромисс заключается в том, что у вас больше нет единого базового класса. A Model<Appointment>
- это не то же самое, что и Model<Customer>
. Я вообще не считаю, что это проблема, потому что там мало общих функциональных возможностей - интерфейсы схожи, но все они работают с разными типами.
Если вам нужна общая база, вы можете, конечно, обмануть и реализовать интерфейс на основе object
, который выполняет те же общие задачи. Например, что-то в духе (непроверено, но идея там):
interface IModelEntity
{
object Read(Guid ID);
}
class Model<T> : IModelEntity
{
public T Read(Guid ID)
{
return this.OnRead(ID); // Call the abstract read implementation
}
object IModelEntity.Read(Guid ID)
{
return this.OnRead(ID); // Call the abstract read implementation
}
protected abstract virtual T OnRead(Guid ID);
}
class Appointment : Model<Appointment>
{
protected override Appointment OnRead(Guid ID) { /* Do Read Stuff */ }
}
Ответ 2
Будет ли это работать?
public abstract T Read<T>(Guid ID) where T : IAppointment;
Ответ 3
Вам нужно вставить и бросить. Интересно, но почему этот метод является общим?
return (T)(object)appointment;
Ответ 4
Вы должны набрать object
сначала, а затем T
. Причина кроется в том, что object
находится в верхней части цепочки наследования. Прямой корреляции от Appointment
до T
нет; поэтому вам нужно отступить до object
, а затем вернуться к T
.
Я предоставил этот ответ, чтобы дать объяснение, почему оператор возврата не будет работать, если он не будет дважды применен - и в поддержку ответов, данных Хаосом и Грегом
Ответ 5
Сначала, я бы предложил вам превратить ваш базовый класс в интерфейс. Если это вариант для вас, это также уменьшится в чуть менее загроможденном коде, поскольку вы можете избавиться от ключевых слов abstract
и public
в объявлении интерфейса и опустить override
в реализующих классах.
Второй, как предлагает ваша реализация Appointment.Read
, вы можете изменить сигнатуру метода Read
, чтобы вернуть объект модели.
Оба предложенных изменения приведут к следующему:
public interface IModel
{
void Create();
IModel Read(Guid ID);
void Update();
void Delete();
}
Третий, мне кажется, что Read
действительно должен быть factory. В вашем текущем коде вам нужно сначала создать экземпляр объекта Appointment
, прежде чем вы сможете вызвать метод Read
для извлечения другого объекта Appointment
. Это кажется неправильным для меня, с точки зрения дизайна класса.
Как взять Read
из базового класса/интерфейса и предоставить его как статический метод во всех производных/реализующих классах? Например:
public class Appointment : IModel
{
public static Appointment Read(Guid ID)
{
return new Appointment()
{
...
};
}
}
Вы также можете рассмотреть возможность перемещения Read
в статический класс (factory); однако тогда он должен быть достаточно умным, чтобы знать, какой объект он должен вернуть. Это будет работать, например. если бы у вас была таблица в вашей БД, которая сопоставила бы идентификатор GUID с соответствующим типом объекта.
Изменить. Последнее предложение выше:
В-третьих, если это так до сих пор, следующий вопрос будет заключаться в том, должен ли вместо Read
быть статический метод. Если это так, его можно было бы сделать static
и переместить в статический класс Model
. Затем метод будет действовать как метод factory, который создает IModel
объекты из БД:
Guid guid = ...;
IModel someModel = Model.Read(guid);
Ответ 6
В этом дизайне есть что-то напуганное.
Независимо от того, является ли шаблон Model
шаблоном, размещение параметра шаблона в методе Read
не имеет большого смысла в качестве метода экземпляра.
Обычно у вас будет что-то вроде того, что размещал Грег Д.
Ответ 7
Если public abstract T Read<T>(Guid ID);
of Model
вернет только производные типы Model
, рассмотрим возможность изменения сигнатуры на
public abstract class Model
{
public abstract void Create();
public abstract Model Read(Guid ID); //<--here
public abstract void Update();
public abstract void Delete();
}
Ответ 8
в вашем классе Appointment добавьте это
public class Appointment<T> : Model where T : Appointment
Ответ 9
вы также можете позвонить:
var x = myModel.Read<Appointment>();