Есть класс, содержащий оператор приведения типа int к типу этого класса
class Item
{
public int ID;
public static implicit operator Item(int id)
{
return new Item { ID = id };
}
}
Если написать так
int id = 1;
Item item = id;
то приведение типа срабатывает.
Теперь я хочу набор int-ов преобразовать в набор Item-ов, соответственно я делаю
int[] ids = new int[] { 1, 2, 3 };
Item[] items = ids.Cast<Item>().ToArray();
и получаю
An unhandled exception of type 'System.InvalidCastException' occurred in System.Core.dll Additional information: Unable to cast object of type 'System.Int32' to type 'Item'.
Замена implicit на explicit не помогла.
Как это решить наиболее изящно, и (главное) почему Cast<T> не срабатывает?
Cast
не срабатывает, потому что, если открыть его исходник, мы увидим следующее:
static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source) {
foreach (object obj in source) yield return (TResult)obj;
}
т.е. по сути это эквивалентно
int id = 1;
Item item = (Item)(object)id;
что, естественно, не срабатывает, т. к. object
нельзя преобразовать в ваш тип, да и оператор для явного/неявного преобразования из object
вообще запрещено создавать.
Для того, чтобы ваш оператор использовался, компилятор должен видеть его и генерировать вызов при компиляции:
int[] ids = new int[] { 1, 2, 3 };
Item[] items = ids.Select(i => (Item)i).ToArray();
с уже скомпилированными сборками это не сработает.
PS:
Вы, конечно, можете попытаться написать свой метод для каста:
public static class MyExtensions
{
public static IEnumerable<TOut> MyCast<TIn, TOut>(this IEnumerable<TIn> sourse)
{
foreach (TIn e in sourse) yield return (TOut)e;
}
}
Но это не скомпилируется, т. к. нельзя произвольный тип TIn
преобразовать в произвольный TOut
.
Вы можете попытаться добавить ограничение на типы:
public static class MyExtensions
{
public static IEnumerable<TOut> MyCast<TIn, TOut>(
this IEnumerable<TIn> sourse) where TOut : TIn
{
foreach (TIn e in sourse) yield return (TOut)e;
}
}
и этот класс даже скомпилируется, но вы не сможете им воспользоваться:
Item[] items = ids.MyCast<int, Item>().ToArray();
потому что Item
не является (наследником) int
.
Ну и, конечно, если уж вам на столько сильно нужно решение этой задачи, что вы готовы воспользоваться рефлексией или динамической типизацией, то можно придумать какое-то такое решение, которое найдет метод по его сигнатуре, создаст и него делегат и закеширует его:
public static class MyExtensions
{
public static IEnumerable<TOut> MyCast<TIn, TOut>(this IEnumerable<TIn> sourse)
{
var castMethod = CastImpl<TIn, TOut>.CastMethod;
foreach (TIn e in sourse) yield return castMethod(e);
}
static class CastImpl<TIn, TOut>
{
public static readonly Func<TIn, TOut> CastMethod;
static CastImpl()
{
var attr = MethodAttributes.Static
| MethodAttributes.Public
| MethodAttributes.HideBySig
| MethodAttributes.SpecialName;
var names = new[] { "op_Implicit", "op_Explicit" };
var method = typeof(TIn).GetMethods()
.Concat(typeof(TOut).GetMethods())
.Where(mi => mi.Attributes == attr
&& names.Contains(mi.Name)
&& mi.ReturnType == typeof(TOut)
&& mi.GetParameters() is ParameterInfo[] pi
&& pi.Length == 1
&& pi[0].ParameterType == typeof(TIn))
.FirstOrDefault();
if (method == null) throw new NotSupportedException(
$"Cast {typeof(TIn)} => {typeof(TOut)} not supported!");
CastMethod = (Func<TIn, TOut>)method.CreateDelegate(typeof(Func<TIn, TOut>));
}
}
}
Тогда, конечно, это будет работать:
int[] ids = new int[] { 1, 2, 3 };
Item[] items = ids.MyCast<int, Item>().ToArray();
То же самое с использованием Expression
будет выглядеть короче, за счет того, что метод для конвертации будет найден автоматически:
public static class MyExtensions
{
public static IEnumerable<TOut> MyCast<TIn, TOut>(this IEnumerable<TIn> sourse)
{
var castMethod = CastImpl<TIn, TOut>.CastMethod;
foreach (TIn e in sourse) yield return castMethod(e);
}
static class CastImpl<TIn, TOut>
{
public static readonly Func<TIn, TOut> CastMethod;
static CastImpl()
{
var param = Expression.Parameter(typeof(TIn), "i");
var body = Expression.Convert(param, typeof(TOut));
var lambda = Expression.Lambda(body, new[] { param });
CastMethod = (Func<TIn, TOut>)lambda.Compile();
}
}
}
Причем этот метод получается даже более универсальным, в нем сработает и привычный каст (в отличие от предыдущего):
var cars = items.MyCast<Vehicle, Car>();
int[] ids = new int[] { 1, 2, 3 };
var test = Array.ConvertAll(ids, (p => (Item)p));//Вариант 1
var test2 = ids.Select(p => (Item)p).ToArray(); //Вариант 2
Проверял на твоем же коде.
Абсолютно успешно срабатывают оба. Выбирай любой который тебе больше по душе.
Не забудь подключить неймспейс: using System.Linq
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Пишу тестовый веб-сайт, где пробую разные аспекты CQRS (сначала это была самопальная реализация cqrs, потом попробовал MediatR)
У меня имеется база данных и мне надо отсортировать данные в нейЯ создал массив строк и хочу сравнить их с нужным столбцом в каждой строке...
Можно ли узнать с каким IP пользователь выходит в глобальную сеть? Использование сервисов не вариант потому, что например если он использует...