Страницы

Поиск по вопросам

вторник, 31 декабря 2019 г.

Cast<T> для набора элементов приводящихся к Т

#c_sharp


Есть класс, содержащий оператор приведения типа 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().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 не срабатывает?
    


Ответы

Ответ 1



Cast не срабатывает, потому что, если открыть его исходник, мы увидим следующее: static IEnumerable CastIterator(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 MyCast(this IEnumerable sourse) { foreach (TIn e in sourse) yield return (TOut)e; } } Но это не скомпилируется, т. к. нельзя произвольный тип TIn преобразовать в произвольный TOut. Вы можете попытаться добавить ограничение на типы: public static class MyExtensions { public static IEnumerable MyCast( this IEnumerable sourse) where TOut : TIn { foreach (TIn e in sourse) yield return (TOut)e; } } и этот класс даже скомпилируется, но вы не сможете им воспользоваться: Item[] items = ids.MyCast().ToArray(); потому что Item не является (наследником) int. Ну и, конечно, если уж вам на столько сильно нужно решение этой задачи, что вы готовы воспользоваться рефлексией или динамической типизацией, то можно придумать какое-то такое решение, которое найдет метод по его сигнатуре, создаст и него делегат и закеширует его: public static class MyExtensions { public static IEnumerable MyCast(this IEnumerable sourse) { var castMethod = CastImpl.CastMethod; foreach (TIn e in sourse) yield return castMethod(e); } static class CastImpl { public static readonly Func 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)method.CreateDelegate(typeof(Func)); } } } Тогда, конечно, это будет работать: int[] ids = new int[] { 1, 2, 3 }; Item[] items = ids.MyCast().ToArray(); То же самое с использованием Expression будет выглядеть короче, за счет того, что метод для конвертации будет найден автоматически: public static class MyExtensions { public static IEnumerable MyCast(this IEnumerable sourse) { var castMethod = CastImpl.CastMethod; foreach (TIn e in sourse) yield return castMethod(e); } static class CastImpl { public static readonly Func 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)lambda.Compile(); } } } Причем этот метод получается даже более универсальным, в нем сработает и привычный каст (в отличие от предыдущего): var cars = items.MyCast();

Ответ 2



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

Комментариев нет:

Отправить комментарий