Я думаю, что у вас может быть несколько вариантов в зависимости от ваших требований.
- Вы можете попробовать привести входящее
TEntity
к TResult
. Это потребует ограничения типа на TResult
и довольно сложного.
- Вы можете проверить, имеет ли
TEntity
тип TResult
, и если это так, вы вернете приведенный тип. В противном случае вернуть значение по умолчанию TResult
. Для этого не требуется ограничение типа, и я думаю, что это лучшее, что вы могли бы получить без ограничения.
- Вы можете отказаться от значения параметра по умолчанию все вместе и перегрузить функцию. Лично я думаю, что это будет вашим лучшим выбором.
Вот примеры вариантов, которые я перечислил выше:
Вариант 1: Приведение с ограничением типа.
public async Task<TResult> GetSingleAsync<TResult, TNewEntity>(Expression<Func<TNewEntity, TResult>> selector = null)
where TNewEntity: TEntity, TResult
{
IQueryable<TNewEntity> query = this._collection;
if (selector == null) selector = entity => (TResult) entity;
return await query.Select(selector).SingleOrDefaultAsync();
}
Здесь вы видите уродливое ограничение типа, о котором я говорил. Вы должны ввести новый тип, чтобы убедиться, что ограничение вашего класса имеет тип TResult
.
Вариант 2: Проверка типа и приведение.
public async Task<TResult> GetSingleAsync<TResult>(Expression<Func<TEntity, TResult>> selector = null)
{
IQueryable<TNewEntity> query = this._collection;
// I would use pattern matching here if I could, but unfortunately it looks like
// expression trees cannot have pattern matching so we have to box then cast.
if (selector == null) selector = entity => entity is TResult ? (TResult)(object) entity : default(TResult);
return await query.Select(selector).SingleOrDefaultAsync();
}
При этом он будет пытаться привести сущность к правильному типу, но если это не удастся, он вернет значение по умолчанию для TResult
, которое будет null
, если это ссылочный тип. Это все равно повлечет за собой ту же проблему, с которой вы сталкивались ранее, но в тех случаях, когда приведение прошло успешно, это может быть тем, что вы хотите.
Вариант 3: Перегрузка метода.
// New method with no selector. Notice the return type is now TEntity
public async Task<TEntity> GetSingleAsync(){
return GetSingleAsync(x => x); // This now works because TResult is TEntity.
}
// Original method, but now it requires the selector
public async Task<TResult> GetSingleAsync<TResult>(Expression<Func<TEntity, TResult>> selector)
{
IQueryable<TNewEntity> query = this._collection;
return await query.Select(selector).SingleOrDefaultAsync();
}
Лично это похоже на тот вариант, который вам нужен, и тот, который я бы использовал. По сути, он обладает той же функциональностью, что и параметр по умолчанию, но теперь требует, чтобы, если селектор не был предоставлен, запрос возвратил TEntity
. Однако я не знаю всех ограничений вашей проблемы или требуется ли параметр по умолчанию.
Примечание: это похоже на то, что LINQ делает с дополнительными селекторами. Вот источник нескольких расширений ToDictionary
:
public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer) {
return ToDictionary<TSource, TKey, TSource>(source, keySelector, IdentityFunction<TSource>.Instance, comparer);
}
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector) {
return ToDictionary<TSource, TKey, TElement>(source, keySelector, elementSelector, null);
}
Обратите внимание, как они передают функцию идентификации для elementSelector
, и каждый TElement
по сути просто заменяется на TSource
в первом методе.
Вывод:
Могут быть и другие варианты, которые здесь не перечислены, но это лучшие из тех, которые я мог бы придумать. Я проверил, скомпилируются ли они, но не запустил ни одного из них.