Оператор как и универсальные классы

Я пишу компилятор .NET "на лету" для сценариев CLR и хочу, чтобы метод выполнения сделал универсальный приемлемым:

object Execute()
{
  return type.InvokeMember(..);
}

T Execute<T>()
{
  return Execute() as T; /* doesn't work:
  The type parameter 'T' cannot be used with the 'as' operator because
  it does not have a class type constraint nor a 'class' constraint */

  // also neither typeof(T) not T.GetType(), so on are possible

  return (T) Execute(); // ok
}

Но я думаю оператор as будет очень полезно: если тип результата не T метод вернется null, а не исключение! Можно ли это сделать?

5 ответов

Решение

Вам нужно добавить

where T : class

к объявлению вашего метода, например

T Execute<T>()  where T : class
{

Кстати, в качестве предположения, что универсальная обертка на самом деле не добавляет особой ценности. Звонящий может написать:

MyClass c = whatever.Execute() as MyClass;

Или если они хотят бросить на неудачу:

MyClass c = (MyClass)whatever.Execute();

Общий метод-оболочка выглядит следующим образом:

MyClass c = whatever.Execute<MyClass>();

Все три версии должны указывать одни и те же три сущности, только в разных порядках, поэтому ни одна из них не является более простой или более удобной, и, тем не менее, общая версия скрывает происходящее, тогда как каждая из "сырых" версий дает понять, будет ли быть броском или null,

(Это может быть неуместно для вас, если ваш пример упрощен от вашего фактического кода).

Вы не можете использовать as оператор с универсальным типом без ограничений. Так как as Оператор использует null для представления того, что он не относится к типу, его нельзя использовать для типов значений. Если вы хотите использовать obj as T, T должен быть ссылочным типом.

T Execute<T>() where T : class
{
  return Execute() as T;
}

Этот небольшой фрагмент кода является безопасной заменой ключевого слова as:

return Execute() is T value ? value : default(T)

Он использует функцию сопоставления с образцом, представленную в C# 7. Используйте ее, если вы не хотите ограничивать универсальный параметр ссылочным типом

Есть ли вероятность, что Execute() может вернуть тип значения? Если это так, то вам нужен метод Earwicker для типов классов и другой универсальный метод для типов значений. Может выглядеть так:

Nullable<T> ExecuteForValueType<T> where T : struct

Логика внутри этого метода скажет

object rawResult = Execute();

Затем вам нужно получить тип rawResult и посмотреть, можно ли его присвоить T:

Nullable<T> finalReturnValue = null;

Type theType = rawResult.GetType();
Type tType = typeof(T);

if(tType.IsAssignableFrom(theType))
{
    finalReturnValue = tType;     
}

return finalReturnValue;

Наконец, заставьте ваше исходное сообщение Execute выяснить, какой тип T имеет (тип класса или структура), и вызовите соответствующую реализацию.

Примечание: это из грубой памяти. Я сделал это около года назад и, вероятно, не помню каждой детали. Тем не менее, я надеюсь, что направить вас в общем направлении поможет.

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

int result = Execute<int>();

не сильно отличается от

int result = (int)Execute();

Вы можете использовать модификатор out, чтобы записать результат в переменную в области видимости вызывающего и вернуть логический флаг, чтобы сказать, удалось ли это:

bool Execute<T>(out T result) where T : class
{
    result = Execute() as T;
    return result != null;
}
Другие вопросы по тегам