【问题标题】:Converting IList to generic TResult, which can be IEnumerable or object将 IList 转换为通用 TResult,可以是 IEnumerable 或对象
【发布时间】:2020-01-29 16:11:13
【问题描述】:

我正在尝试通过创建序列化程序来获得一些使用泛型的经验。

我有一个反序列化的方法,我想使用泛型。它应该能够接收对象或IEnumerable,例如List<Person>Person[] 等。Deserialize<TResult> 正在工作.. 有点.. 但我在弄清楚如何实际上将我的结果返回为TResult

这是我所拥有的:

    public static TResult Deserialize<TResult>(StreamReader inputStream)
    {
        if (inputStream.EndOfStream) return default(TResult);

        if (typeof(TResult).IsEnumerable())
        {
            Type itemType = typeof(TResult).GetItemType();
            IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(itemType));

            MethodInfo deserializeMethod = 
                typeof(SimpleFixedWidthSerializer)
                    .GetMethod("Deserialize", new[] { typeof(StreamReader) })
                    .MakeGenericMethod(new[] { itemType });

            object item = null;
            do
            {
                item = deserializeMethod.Invoke(null, new[] { inputStream });
                if (item != null)
                    list.Add(item);
            } while (item != null);

            list.Dump();
            return (TResult)list;
        }

        ...
    }

查看结果的Dump(),我可以看到它被正确地反序列化为Person 类型的System.Collection.Generic.List,并且每个Person 都被单独正确地反序列化.. 但我似乎无法弄清楚了解如何从IList 到我的TResult。方法调用示例:

string testInput = "...";
using (MemoryStream mStream = new MemoryStream(Encoding.UTF8.GetBytes(testInput)))
using (StreamReader sr = new StreamReader(mStream))
    SimpleFixedWidthSerializer.Deserialize<Person[]>(sr).Dump();

这会导致

Unable to cast object of type 'System.Collections.Generic.List 1[UserQuery+Person]' to type 'Person[]'

有谁知道如何将我的IList 正确转换为我的TResult

【问题讨论】:

  • 既然您对typeof(TResult).IsEnumerable() 没问题,只需添加一个switch/case,其中包含您可能拥有的所有类型作为泛型类型。
  • 既然返回的是硬编码列表,为什么要使用泛型?
  • 如果您在将其转换为 TResult 并返回之前对 IList 执行了 .ToArray() 会有帮助吗? List&lt;Person&gt;Person[] 但我不认为 IListPerson[]
  • @panoskarajohn - 我没有返回硬编码列表,而是将固定宽度的文件解析为指定的任何类型

标签: c# .net generics casting


【解决方案1】:

改变你的方法,这样它:

  • list 声明为List&lt;&gt; 类型的对象(即您可以使用var 关键字)。您需要这个,因为 IList 没有 ToArray() 方法。
  • TResult 类型限制为类类型(这是为了以后ToArray() 调用工作所必需的。
  • 通过调用 ToArray() 将您的列表转换为数组,然后再返回。
  • 使用 as 关键字进行强制转换,以便在运行时解析而不是被编译器捕获。

这些更改会将您的方法更改为:

public static TResult Deserialize<TResult>(StreamReader inputStream) where TResult : class
{
    if (inputStream.EndOfStream) return default(TResult);

    if (typeof(TResult).IsEnumerable())
    {
        Type itemType = typeof(TResult).GetItemType();
        var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(itemType));

        MethodInfo deserializeMethod = 
            typeof(SimpleFixedWidthSerializer)
                .GetMethod("Deserialize", new[] { typeof(StreamReader) })
                .MakeGenericMethod(new[] { itemType });

        object item = null;
        do
        {
            item = deserializeMethod.Invoke(null, new[] { inputStream });
            if (item != null)
                list.Add(item);
        } while (item != null);

        list.Dump();
        return list.ToArray() as TResult;
    }

    ...
}

【讨论】:

  • IList 更改为var,即:var list = Activator.CreateInstance(typeof(List&lt;&gt;).MakeGenericType(itemType)); 导致list 输入object,这不起作用
【解决方案2】:

当你调用Deserialize&lt;Person[]&gt; 时,TResult 是一个数组类型。由于对象listListList 不是数组,所以这个转换:(TResult)list 失败。相反,请致电Deserialize&lt;List&lt;Person&gt;&gt;Deserialize&lt;IList&lt;Person&gt;&gt;

问题的根源是当TResultIsEnumerable 时,在所有情况下都会返回List。但是所有Enumerable 都不是List(例如:数组)。

【讨论】:

    【解决方案3】:

    好的,这是第二次尝试。这个想法是你不想无缘无故地让你的代码复杂化,所以我简化了List的创建。然后将所有对象添加到其中,并在最后返回之前进行所有投射。这需要第二个通用参数,我称之为TItemType

    public static TResult Deserialize<TResult, TItemType>() where TResult : class
    {
        if (inputStream.EndOfStream) return default(TResult);
    
        if (typeof(TResult).IsEnumerable())
        {
            var list = new List<object>();
    
            MethodInfo deserializeMethod = 
                typeof(SimpleFixedWidthSerializer)
                    .GetMethod("Deserialize", new[] { typeof(StreamReader) })
                    .MakeGenericMethod(new[] { itemType });
    
            object item = null;
            do
            {
                item = deserializeMethod.Invoke(null, new[] { inputStream });
                if (item != null)
                    list.Add(item);
            } while (item != null);
    
            return typeof(TResult).IsArray
                ? list.Cast<TItemType>().ToArray() as TResult
                : list.Cast<TItemType>().ToList() as TResult;
        }
        ...
    }
    

    【讨论】:

    • 这很有帮助 - 我走了一条稍微不同的路线,并创建了第二个名为 DeserializeEnumerable&lt;TResult, TType&gt; 的方法,与此类似,但仅使用 var list = new List&lt;TType&gt;() 并且我的循环以 TType item = null; 开始。我必须做任何类型的演员的唯一地方是最后,我正在做return list.AsEnumerable() as TResult;。我仍然需要进行 IsArray 检查,但它可以工作并且更容易理解!特别是因为我不需要在我的循环中进行反射并且可以做item = Deserialize&lt;TType&gt;(inputStream);
    【解决方案4】:

    我走的最后一条路线是这样的,看起来效果还不错:

            public static TResult Deserialize<TResult>(BufferedStreamReader inputStream) where TResult : class
            {
                if (inputStream.EndOfStream) return default;
    
                if (typeof(TResult).IsEnumerable()) //arrays, lists, etc are a special cases
                {
                    Type itemType = typeof(TResult).GetItemType();
    
                    MethodInfo methodInfo = typeof(FixedWidthSerializer)
                        .GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
                        .FirstOrDefault(m => m.Name == "DeserializeEnumerable")
                        .MakeGenericMethod(new[] { typeof(TResult), itemType });
    
                    return methodInfo.Invoke(null, new[] { inputStream }) as TResult;
                }
                ...
             }
            private static TResult DeserializeEnumerable<TResult, TType>(BufferedStreamReader inputStream) //ignore the IDE, this is being called through reflection
                where TResult : class
                where TType : class
            {
                var list = new List<TType>();
    
                TType item;
                while ((item = Deserialize<TType>(inputStream)) != null)
                    list.Add(item);
    
                if (typeof(TResult).IsArray)
                    return list.ToArray<TType>() as TResult;
                return list.AsEnumerable<TType>() as TResult;
            }
    

    【讨论】:

    • 我会将list 的演员表留给IList。然后你可以摆脱addMethod。如果您认为这更简单,您可以使用Array.CreateInstancelist.CopyTo 和一些强制转换来代替toArray。我认为没有任何绕过IsArray 检查的方法,即使这样也不会解决所有情况。如果你反序列化 HashSet&lt;Person&gt;LinkedList&lt;Person&gt; 会发生什么?
    • @JoshuaRobinson - 我确实希望可以创建泛型 where 子句充当重载的方法,这将使处理变得更容易。LinkedList 和 HashSet 不会像它们一样工作都实现了 IEnumerable?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多