匿名类型 - 有哪些独特的特征吗?

31

有没有什么方法可以确定一个类型是否为匿名类型?比如说接口等。

目标是创建类似于以下的内容...

//defined like...
public static T Get<T>(this IAnonymous obj, string prop) {
    return (T)obj.GetType().GetProperty(prop).GetValue(obj, null);
}
//...

//And then used like...
var something = new { name = "John", age = 25 };
int age = something.Get<int>("age");

或者这只是匿名类型的美丽之处?因为它采用了新的形式,所以没有什么可以标识它自己的东西吗?

注意 - 我意识到你可以为object类编写扩展方法,但在我看来,那似乎有点过度设计。

3个回答

62

编辑:以下列表适用于C#匿名类型。VB.NET有不同的规则 - 特别是它可以生成可变的匿名类型(默认情况下会生成)。Jared在评论中指出命名样式也不同。基本上,这一切都相当脆弱...

你无法在泛型约束中识别它,但是:

  • 它将是一个类(而不是接口、枚举、结构等)
  • 它将应用CompilerGeneratedAttribute
  • 它将重写Equals、GetHashCode和ToString方法
  • 它将在全局命名空间中
  • 它不会嵌套在另一个类型中
  • 它将是internal访问级别
  • 它将是sealed的
  • 它将直接继承自object
  • 它将是泛型的,具有与属性数量相同的类型参数。(你可以有一个非泛型的匿名类型,没有属性。但这有点无意义。)
  • 每个属性都将有一个类型参数,名称包括属性名称,并且将是该类型参数的类型,例如Name属性成为类型为<>_Name的属性
  • 每个属性都将是公共的和只读的
  • 对于每个属性,都将有一个相应的只读私有字段
  • 没有其他属性或字段
  • 将有一个构造函数,它将按照类型参数的顺序,为每个类型参数接受一个参数
  • 每个方法和属性都将应用DebuggerHiddenAttribute
  • 类型的名称将以"<>"开头,并包含"AnonymousType"

然而,规范只能保证很少的内容 - 因此在下一个版本的编译器中或者使用Mono等情况下,所有内容都可能会发生改变。


9
“<>” 名称前缀仅适用于 C#。在 VB 中,名称前缀将为 “VB$AnonymousType”。 - JaredPar
2
哦,这值得知道。我敢说其他很多东西只适用于C# - 特别是VB的可以是可变的。将进行编辑以反映这一点。 - Jon Skeet
我扫描了其余的条目,只有另外两个VB错误。这些字段没有<>_名称前缀。此外,VB版本还有一个额外的私有属性称为AtDebuggerDisplay,用于调试时的性能优化。 - JaredPar
好的。也许我会在某些时候修订答案,加入普通/C#/VB部分。但它仍然会很脆弱 :( - Jon Skeet
一个更好的解决方案是使匿名类型可命名。或者用鸭子类型来解决 :) - JaredPar
3
我不知道为什么会有人这样做,但是如果传入的匿名类型是 "new {}",它将不是泛型且将具有“默认”构造函数。 - Matthew Whited

4

据我回忆,这里有一个[CompilerGenerated]标记... 2秒

另外,名称将会很奇怪,而且它将是一种通用类型 ;-p

实际上,对于“get”等操作,我可能只会使用静态(非扩展)方法。

如果你只想从匿名类型的实例中获取值(在以后的某个时间点),lambda 可能是最好的选择 - 请注意,你需要一些技巧来完成这个过程:

    static void Main()
    {
        var foo = new { name = "John", age = 25 };
        var func = Get(foo, x => x.age);
        var bar = new { name = "Marc", age = 30 };
        int age = func(bar);
    }
    // template here is just for type inference...
    static Func<TSource, TValue> Get<TSource, TValue>(
        TSource template, Func<TSource, TValue> lambda)
    {
        return lambda;
    }

(关于评论的编辑)这个属性确实是存在的:

        var foo = new { A = "B" };
        Type type = foo.GetType();

        CompilerGeneratedAttribute attrib = (CompilerGeneratedAttribute) Attribute.GetCustomAttribute(
            type, typeof(CompilerGeneratedAttribute)); // non-null, therefore is compiler-generated

没有,没有编译器生成的属性。 - JaredPar
@JaredPar:在我的小测试中肯定会有。 - Jon Skeet
1
[CompilerGenerated] 区分了一个比匿名类型更大的宇宙。 - JaredPar
@Jon,证据也不是很充分。许多其他工具都会添加CompilerGenerated属性。这个解决方案只适用于严格的基本解决方案。一旦您向应用程序添加第三方DLL,就会增加出错的可能性。 - JaredPar
1
@Jared:是的,这取决于你如何运用这个知识。如果它只涉及到你自己的程序集,并且你知道唯一涉及的工具就是C#编译器,那还好……但是除此之外,它非常容易出问题。 - Jon Skeet
显示剩余3条评论

0

就扩展方法而言,无法区分匿名类型。扩展方法通过为编译时可命名的类型指定方法来工作。匿名类型是不可命名的,因此在编译时不可见。这使它们与扩展方法不兼容。


实际上,您可以通过lambda等方式实现。但是,在(例如)IEnumerable<T>等上的通用扩展方法是将强类型匿名类型(T)从方法传递出去并以有用的方式处理它(通过Func<T>,Action<T>等)的主要机制。 - Marc Gravell
(好的,这不一定是扩展方法,但对于LINQ有帮助)。例如... list.Sum(x => x.Foo),其中list是定义了Foo的匿名类型。 - Marc Gravell
是的,但匿名类型的使用在lambda中,而不是扩展方法中。无法在扩展方法中静态访问匿名类型成员,只能在lambda中访问。 - JaredPar
@JaredPar - 你是唯一一个谈论静态类型发生在哪里的人。关键是,通过lambda和通用方法(通常是扩展,但不一定),你可以完成任务,而这正是我们的目标。 - Marc Gravell
@Jared:抱歉,我并不是在试图声称这是静态类型 - 只是传递匿名类型到扩展方法仍然非常有用,而且编写至少主要与匿名类型有关的方法也很有用。 - Jon Skeet
显示剩余5条评论

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