【问题标题】:how to refer to an item from anonymous type如何引用匿名类型的项目
【发布时间】:2011-12-10 00:24:00
【问题描述】:

我有这样的代码

var results =   (from c in Customers  
                    join o in Orders  
                    on c.Id equals o.CustomerId  
                    join p in Products  
                    on p.Id equals o.ProductId  
                    select new   
                    {  
                        CustomerId = c.Id,     // this is a GUID  
                        OrderId = o.Id,        // this is a GUID    
                        ProductName = p.ProductName,  
                     }).ToList();  

假设我想获取订购具有 name = foo 的产品的所有客户 ID 的列表 我的问题是,因为它是匿名类型,我如何在我想在结果上运行的任何 Linq 查询中引用产品名称?

【问题讨论】:

  • 结果在编译时是强类型的。它将具有您在选择投影中指定的名称和类型的属性。
  • 您通过名称来引用 ProductName -- ProductName。我不明白问题是什么;你能澄清一下这个问题吗?
  • 我想我只是对匿名类型的范围感到困惑,并且不确定这种类型在方法或类范围内的可见性。我的想法是,鉴于此类型未在任何地方声明,编译器如何确定我在说哪种类型。
  • @palmsnow msdn.microsoft.com/en-us/library/bb397696.aspx 有大量在线文档。
  • @palmsnow 匿名类型的可见性是一个明显的限制。例如,您不能从非匿名方法返回它们,因为该方法需要有一个声明来指定(并因此命名)其返回类型。

标签: c# linq anonymous-types


【解决方案1】:
var filteredResults = results.Where(r => r.ProductName == "X");

编译器的类型推断会为您处理。您问题的完整答案:

var customerIds = results
    .Where(r => r.ProductName == "X")
    .Select(r => r.CustomerId)
    .Distinct()
    .ToList();

var customerIds = (from r in results
                  where r.ProductName == "X"
                  select r.CustomerId)
    .Distinct()
    .ToList();

编辑

关于类型推断的一些思考

要从称为list 的字符串序列中选择长度,您可以使用经典的静态方法语法或作为扩展方法调用Select

Enumerable.Select<string, int>(list, s => s.Length)
list.Select<string, int>(s => s.Length)

由于类型推断,您不需要类型参数:

Enumerable.Select(list, s => s.Length)
list.Select(s => s.Length)

在这种情况下,编译器可以通过查看方法参数来证明类型参数是stringint,并且它会代表您提供这些类型参数,而无需您将它们输入到源代码中。

对于匿名类型,您不能提供第一个类型参数,因为该类型没有名称供您在源代码中使用(即毕竟,“匿名”的意思是:“没有名字”)。 (因此,您可以看到,匿名类型和类型推断对于首先实现 linq 来说都是至关重要且密切相关的先决条件。)

如果您查看上述匿名类型示例的 IL,您会发现编译器实际上已经为该类型指定了一个名称(其中包含 C# 标识符中的非法字符)。当您调用 Select 时,编译器从可枚举 (IEnumerable&lt;CrazilyNamedAnonymousType&gt;) 的类型推断出第一个类型参数应该是匿名类型,并且与字符串示例一样,它代表您提供该值。

【讨论】:

  • @Gent 我已经充实了答案,如果您想添加任何内容,请告诉我。
  • 谢谢,我相信你的回答会在这个用户离开很久之后增加价值。
【解决方案2】:

在生成此匿名类型化结果的方法中,您可以继续引用结果,就像定义了具体类型一样。

var customersWithFoo = results.Where(r => r.ProductName == "foo")
                              .Select(r => r.CustomerId);

如果您要返回此方法的原始查询结果out,并且然后希望进一步查询它或以其他方式以编程方式访问元素,定义一个类型。

class QueryResult 
{
   /* relevant properties */
}

然后在您的原始查询中投影到该类型,并从您的方法中返回该类型的序列。

public IEnumerable<QueryResult> GetResults()
{
     var results = ...
                   select new QueryResult 
                   {
                       // properties
                   };

     return results;
}

【讨论】:

  • 感谢您的回复,这有助于理解这一点
【解决方案3】:

跟进您的评论:

我对匿名类型的范围感到困惑

首先让我们明确定义“范围”。 类型范围被定义为程序文本的区域,在该区域中类型可以通过其非限定名称来引用

有了这个定义,匿名类型的范围就很明显了。 没有程序文本区域,其中匿名类型可以通过其名称来引用,因为它没有名称。匿名类型根本没有作用域。您不必担心它的范围;它没有范围。

匿名类型的字段也没有范围,但出于不同的原因。匿名类型的字段具有名称,但通过其非限定名称引用它们是不合法的,因此每个字段的范围都是空的。

我不确定这种类型在方法或类范围内的可见性。

再次,让我们清楚地定义我们的术语。实体的范围可能包括定义声明空间的实体。这些声明空间可以声明与原始实体同名的实体。这些实体有自己的范围,可以嵌套在原始实体的范围内。

在这种情况下,嵌套较多的实体可能会“隐藏”嵌套较少的实体。以这种方式隐藏的实体被称为在特定文本位置“可见”。

匿名类型没有范围,它显然不能通过名称隐藏,因为它没有名称。询问匿名类型是否“可见”不是明智之举; “可见性”只对有名字的东西有意义。

我的想法是,鉴于此类型未在任何地方声明,编译器如何确定我在说哪种类型?

编译器会记录您在程序中使用匿名类型的每个文本位置。如果这些位置中的任何两个引用具有相同字段名称、相同字段类型且字段以相同顺序出现的匿名类型那么这两个位置被视为相同的匿名类型的用法。

然后编译器可以为您在程序集中使用的每个唯一匿名类型发出一种类型。它如何做到这一点的细节令人着迷(*)。如果您有兴趣,我建议您使用 ILDASM 查看您的程序集,看看我们是如何做到的。

如果您在两个不同的程序集中创建“相同”的匿名类型——相同的名称、类型和顺序——那么匿名类型将视为同一类型。匿名类型并非旨在跨程序集边界使用。


(*) 对我来说。

【讨论】:

  • Eric,如果类型具有相同的字段名称和类型,则使用相同的匿名类型是有意义的,但为什么顺序很重要?在我的名义班级中,成员声明的顺序无关紧要。如果答案不适合评论,我可以打开另一个问题。告诉我。
  • @SolutionYogi: stackoverflow.com/questions/2154414/…,来自我、Jon 和我的新同事 Brian,他们刚刚加入了我们的 Roslyn 团队。
  • Brian 的回答并没有告诉我“为什么”,它只是说顺序很重要。关于你的回答,我仍然不相信顺序很重要。在 ToString 实现中,为什么不按字母顺序显示属性?关于乔恩的回答,我不确定使用属性顺序如何让编译器开发人员的生活更轻松。可能是我错过了什么。
  • @sSol 在一种情况下,您必须编写一些东西,根据名称和类型比较两个 Set,另一种是 List。写后者比前者简单
  • @SolutionYogi:但是如果“相同”匿名类型的两个实例的项目顺序不同,那么 which ToString 获胜?现在您已经获得了两个“原始订单”,要让它们保持正常似乎需要做大量的工作。更重要的是:我们希望在方法中本地使用特定的匿名类型,而不是在整个程序集中使用。许多人不知道即使是相同的匿名类型也会在程序集边界内统一,因为他们从来没有理由观察到这一事实。努力使更多的统一成为可能并不是花费预算的最佳方式。
【解决方案4】:

匿名类型将出现在智能感知中,就像“普通”类型一样。在编译时,会创建一个代表您的匿名类型的具体类,因此在运行时几乎没有区别。

这是从您的结果集中查询:

var fooOrders = (from x in results 
                where x.ProductName == "foo"
                select x.CustomerId);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-14
    • 1970-01-01
    • 2010-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多