我需要按照声明的顺序获取匿名类型的所有属性的名称。
为了得到它们,我可以使用Type.GetProperties
方法。它似乎以“正确”的顺序返回属性,但according to MSDN我不能依赖它,因为该顺序可能会有所不同。
我还找到了TypeInfo.DeclaredProperties
属性,它给了我期望的结果,但我找不到任何信息是否保证订单(至少MSDN doesn't explicitly state不保证)。
或者还有其他方法吗?它可能无关紧要,但匿名类型实际上是Expression
的一部分。但我不知道Expression Trees API中的任何内容都可以提供帮助。
我的目标是.NET Standard 2.0,它应该适用于完整的.NET框架,.NET Core和Mono / Xamarin。
示例代码:
static void Main(string[] args)
{
Foo(() => new { Lorem = 1, Ipsum = 2, Dolor = "sit amet" });
}
static void Foo<T>(Expression<Func<T>> expression)
{
var node = (NewExpression)expression.Body;
// seems to work, but GetProperties doesn't guarantee order according to MSDN
var names1 = node.Type.GetProperties().Select(p => p.Name).ToArray();
// names1 = "Lorem", "Ipsum", "Dolor"
// seems to work, but is it "bulletproof"?
var names2 = node.Type.GetTypeInfo().DeclaredProperties.Select(p => p.Name).ToArray();
// names2 = "Lorem", "Ipsum", "Dolor"
}
编辑:我故意没有准确地写出我需要它的原因,因为我发现我的具体场景不必要地解释并希望保持简单的问题。长话短说 - 我正在探索基于解析表达式树的微ORM API的可能性。不是我原来的情况,但这里是一个假设用例的例子,知道顺序很重要(我不是说这样的API用于排序是个好主意!我只是想知道这样的事情是否可行):
// translates to SELECT Column1, Column2 FROM Table ORDER BY Column3, Column1
db.From<Table>()
.Select((x) => new { x.Column1, x.Column2 })
.OrderBy((x) => new { x.Column3, x.Column1 });
对于匿名类型,始终有一个构造函数将所有初始属性值作为参数,其声明顺序与它们相同。您可以使用Position属性获取构造函数参数的位置。
请参阅this question以了解如何获取构造函数参数的名称。然后,您可以将它们与类型的属性进行匹配。
public static class ExtensionMethods
{
public static IEnumerable<PropertyInfo> GetPropertiesInOrder<T>(this T This)
{
var type = typeof(T);
var ctor = type.GetConstructors().Single();
var parameters = ctor.GetParameters().OrderBy( p => p.Position );
foreach (var p in parameters)
{
yield return type.GetProperty(p.Name);
}
}
}
public class Program
{
public static void Main()
{
var o = new { Id = 12, Name="Foo" };
var list = o.GetPropertiesInOrder();
foreach (var p in list)
{
Console.WriteLine("{0} {1}", p.PropertyType.Name, p.Name);
}
}
}
输出:
Int32 Id
String Name
注意:构造函数参数的顺序是可靠的,因为它在C# language specification(第7.6.10.6节)中有详细说明:
匿名对象初始值设定项声明匿名类型并返回该类型的实例。匿名类型是直接从object继承的无名类类型。匿名类型的成员是从用于创建该类型实例的匿名对象初始值设定项推断出的一系列只读属性。具体来说,是表单的匿名对象初始值设定项
new { p1 = e1 , p2 = e2 , … pn = en }
声明表单的匿名类型
class __Anonymous1
{
private readonly T1 f1 ;
private readonly T2 f2 ;
…
private readonly Tn fn ;
public __Anonymous1(T1 a1, T2 a2,…, Tn an) {
f1 = a1 ;
f2 = a2 ;
…
fn = an ;
}
public T1 p1 { get { return f1 ; } }
public T2 p2 { get { return f2 ; } }
…
public T1 p1 { get { return f1 ; } }
public override bool Equals(object o) { … }
public override int GetHashCode() { … }
}
其中每个Tx是相应表达式ex的类型。
这特别将匿名类型定义为属性序列,其中order is significant和构造函数以相同的顺序接受初始化数据。
根据你添加的例子来解释你为什么这样做...我想提出一个简单的答案,假设你有一个X/Y Problem。
你能改变一下(无论如何这都是无效的语法):
// translates to SELECT Column1, Column2 FROM Table ORDER BY Column3, Column1
db.From<Table>()
.Select((x) => new { x.Column1, x.Column2 })
.OrderBy((x) => new { x.Column3, x.Column1 });
对于更像这样的事情?:
// translates to SELECT Column1, Column2 FROM Table ORDER BY Column3, Column1
db.From<Table>()
.Select((x) => new [] { x.Column1, x.Column2 })
.OrderBy((x) => new [] { x.Column3, x.Column1 });
现在注意而不是匿名类型(获取属性的顺序是一个问题)我们正在使用一个数组(获取顺序是微不足道的)。语法非常相似(我们只需添加[]
)但行为更明确。