将匿名类型转换为对象数组?

6

我有这个匿名类型:

var t= new {a=1,b="lalala",c=DateTime.Now};

我该如何将它转换成一个对象数组(每个元素->强制转换为对象)?
因此,最终结果将变成类似这样:
object[]  v = new object[] {1,"lalala",DateTime.Now};

编辑

p.s. 这只是一个关于学习如何将一种类型转换为另一种类型的知识问题。我知道我可以从一开始就初始化对象数组。但这是一个学习问题。

抱歉没有提到它。

顺序很重要...为什么?因为ConstructorInfo.Invoke接受一个值数组,该数组匹配参数的数量、顺序(!!!)和类型(在默认绑定器的约束下)...


3
字段没有隐含的顺序,那么这会如何运作? - leppie
也许在这种情况下,你应该使用一个命名类型。 - Indy9000
你不需要这样做。一个有成员的“类型”和一个数组是有区别的。这就像在说:如何将“System.Windows.Forms.Form”中的所有成员转换为“object[]”。这并不是说这个操作不可行,而是说这个比喻不恰当。 - CodingGorilla
你真正想在这里做什么?你是想将构造函数与匿名类型匹配吗?如果有多个具有相同类型的参数,你将如何消除属性的歧义? - Lee
@Lee,正如我的编辑所说,我只是在学习。 - Royi Namir
6个回答

6
你需要使用反射来实现,主要是通过`Type.GetProperties`方法,应该不会太难,但我不知道是否有内置的方法可以实现。

正如leppie指出的那样,排序并不简单,您需要检查参数的顺序,这至少能让你知道属性类型的所有顺序。如果你只有不同的属性类型,那就没问题了。

如果你不关心顺序,你可以使用:

var array = t.GetType()
             .GetProperties()
             .Select(p => p.GetValue(t, null))
             .ToArray();

编辑:我刚想到一个解决方法,但它是特定于实现的。 C#编译器使用泛型类型生成匿名类型。 因此,new { A = 5, B = "foo" } 实际上将创建一个类似于以下内容的匿名类型:

class <>_Anon<TA, TB>
{
    internal <>_Anon(TA a, TB b)
}

这样你就可以根据泛型属性的通用类型确定属性名称的顺序,然后按照顺序从具体类型中获取属性。但是这样做很丑陋...

using System;
using System.Linq;
using System.Reflection;

class Test    
{
    // Note: this uses implementation details of anonymous
    // types, and is basically horrible.
    static object[] ConvertAnonymousType(object value)
    {
        // TODO: Validation that it's really an anonymous type
        Type type = value.GetType();
        var genericType = type.GetGenericTypeDefinition();
        var parameterTypes = genericType.GetConstructors()[0]
                                        .GetParameters()
                                        .Select(p => p.ParameterType)
                                        .ToList();
        var propertyNames = genericType.GetProperties()
                                       .OrderBy(p => parameterTypes.IndexOf(p.PropertyType))
                                       .Select(p => p.Name);

        return propertyNames.Select(name => type.GetProperty(name)
                                                .GetValue(value, null))
                            .ToArray();

    }

    static void Main()
    {
        var value = new { A = "a", Z = 10, C = "c" };
        var array = ConvertAnonymousType(value);
        foreach (var item in array)
        {
            Console.WriteLine(item); // "a", 10, "c"
        }
    }
}

嗨,我已经编辑了。顺序很重要。抱歉之前没有提到。 - Royi Namir
我知道 :)...只是想看看我能不能自己转换它...但是没有成功...这只是一个挑逗性的问题。(当然是为了学习) - Royi Namir
@RoyiNamir:我现在已经包含了代码。它真的很糟糕...而且肯定是特定于实现的。但它应该适用于MS编译器 :) - Jon Skeet
谢谢。现在我比5分钟前更聪明(使用C#编程语言)。 - Royi Namir

4
public object[] ToPropertyArray(object o)
{
    return o.GetType.GetProperties()
        .Select(p => p.GetValue(o, null))
        .ToArray();
}

编辑:看起来你想从某个匿名类型中调用某种类型的构造函数。看起来唯一可能的方式是如果参数名称与匿名类型的属性名称匹配:

public static T ConstructFromAnonymous<T>(object anon)
{
    //get constructors for type ordered by number of parameters
    var constructors = typeof(T).GetConstructors().OrderByDescending(c => c.GetParameters().Length);

    //get properties from anonymous object
    Dictionary<string, PropertyInfo> properties = anon.GetType()
        .GetProperties()
        .ToDictionary(p => p.Name);

    ConstructorInfo bestMatch = constructors.FirstOrDefault(ci => IsMatch(ci, properties));
    if (bestMatch != null)
    {
        var parameters = bestMatch.GetParameters();
        object[] args = parameters.Select(p => properties[p.Name].GetValue(anon, null)).ToArray();
        return (T)bestMatch.Invoke(args);
    }
    else throw new ArgumentException("Cannot construct type");
}

private static bool IsMatch(ConstructorInfo ci, Dictionary<string, PropertyInfo> properties)
{
    var parameters = ci.GetParameters();
    return parameters.All(p => properties.ContainsKey(p.Name) && p.ParameterType.IsAssignableFrom(properties[p.Name].PropertyType));
}

1

请参见http://blogs.msdn.com/b/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

static void Main()
{
    //Anonymous Type
    var anyType = new
    {
        IntID = 1,
        StringName = "Wriju"
    };

    Type t = anyType.GetType();
    PropertyInfo[] pi = t.GetProperties();
    foreach (PropertyInfo p in pi)
    {
        //Get the name of the prperty
        Console.WriteLine(p.Name);
    }

    //Using LINQ get all the details of Property
    var query = from p in t.GetProperties()
                select p;
    ObjectDumper.Write(query);
}

你应该能够使用GetValue将值添加到数组中,而不是将属性名称写入控制台。


0
如果您的匿名类型始终具有在编译时已知的相同属性,则可以使用明显的显式方法:
var t = new { a = 1, b = "lalala", c = DateTime.Now };
object[] v = new object[] { t.a, t.b, t.c };

0

如果您需要动态创建,反射是最好的选择。如果不需要动态创建,您可以像这样明显地完成它,但我假设您已经考虑过这一点:

var t = new { a = 1, b = "lalala", c = DateTime.Now };

object[] v = new object[] { t.a, t.b, t.c };

您能否提供更深入的问题描述,因为您没有给我们足够的信息,也许如果您不从匿名类型开始,会有更好的解决方案。


0

我认为这比Jon Skeet的解决方案更好,因为它依赖于ToString的结果,而不是匿名类型生成的微妙细节:

var myAnon = new { a = "hi", b185310 = "lo" };
var names = Regex.Matches(myAnon.ToString(), @"([a-zA-Z0-9]+) = ");
var objArray = names.Cast<Match>().Select(name => myAnon.GetType().GetProperty(name.Groups[1].ToString()).GetValue(myAnon, null)).ToArray();

你还可以从myAnonToString方法代码(myAnon.GetType().GetMethod("ToString").GetMethodBody())中读取字符串常量,如果你需要防止匿名类型中的对象被渲染为" = ",从而使简单解析器失效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接