Как условно вызывать общий метод с ограничениями?
Предположим, что у меня есть неограниченный общий метод, который работает на всех типах, поддерживающих равенство. Он выполняет парные проверки равенства и поэтому работает в O (n 2):
public static int CountDuplicates<T>(IList<T> list)
{
/* ... */
}
У меня также есть ограниченный общий метод, который работает только с типами, поддерживающими сортировку. Он начинается с сортировки списка в O (n log n) и затем подсчитывает все дубликаты за один проход:
public static int CountDuplicatesFast<T>(IList<T> list)
where T : IComparable<T>
{
/* ... */
}
Таким образом, вызывающий может выбрать вызов быстрого метода, если статически известно, что тип элементов списка поддерживает упорядочение. Может случиться так, что сам вызывающий объект работает с общим IList<T>
, где T не имеет ограничений, поэтому его единственный вариант для вызова первого (медленного) метода.
Теперь я хочу, чтобы первый метод проверялся во время выполнения, если тип T
фактически реализует интерфейс IComparable<T>
, и если это так, вызовите быстрый метод:
public static int CountDuplicates<T>(IList<T> list)
{
if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
{
return CountDuplicatesFast(list);
}
else
{
/* use the slow algorithm */
}
}
Проблема заключается в том, что компилятор отклоняет вызов CountDuplicatesFast(list)
:
ошибка CS0314: Тип 'T' не может использоваться как параметр типа 'T' в общем типе или методе 'Program.CountDuplicatesFast <T> (System.Collections.Generic.IList <T> )'. Нет никакого преобразования бокса или преобразования параметра типа из 'T' в 'System.IComparable <T> '.
Можно ли убедить компилятор доверять мне, что я знаю, что делаю, и пропустить проверку ограничений?
Ответы
Ответ 1
Здесь можно сделать это с помощью dynamic
:
if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
{
return CountDuplicatesFast((dynamic)list);
}
Или с отражением:
if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
{
var method = typeof(MyType).GetMethod("CountDuplicatesFast");
var generic = method.MakeGenericMethod(typeof(T));
return (int)generic.Invoke(null, new object[] { list });
}
Я не думаю, что есть способ сделать это статически (т.е. без отражения или dynamic
).
Ответ 2
Вы можете использовать класс-помощник и тип dynamic
для пропуска проверки времени компиляции:
sealed class CountDuplicatesFastCaller
{
public int Call<T>(IList<T> list) where T : IComparable<T>
{
return CountDuplicatesFast(list);
}
}
public static int CountDuplicates<T>(IList<T> list)
{
if (typeof (IComparable<T>).IsAssignableFrom(typeof (T)))
{
return ((dynamic) new CountDuplicatesFastCaller()).Call(list);
}
else
{
/* use the slow algorithm */
}
}
Это должно быть быстрее чистого отражения из-за механизмов кэширования DLR.