【问题标题】:Lambda Expression for Class Property类属性的 Lambda 表达式
【发布时间】:2009-08-06 20:38:01
【问题描述】:

有人可以解释为什么以下两个示例中的第一个有效而另一个无效吗?更具体地说,在第一个示例中,T 和 TProperty 之间的关系是如何创建的?

//Example 1  
class SomeClass<T> where T : class
{
    void SomeMethod<TProperty>( Expression<Func<T,TProperty>> expression ){ ... }
}

//Example 2  
class SomeClass
{
    void SomeMethod<T,TProperty>( Expression<Func<T,TProperty>> expression ) 
         where T : class{ ... }
}

鉴于这两个示例,我希望以下实现会起作用,但第二个不会。

//Implementation w/ Example 1  
var sc = new SomeClass<MyDateClass>();
sc.SomeMethod( dt => dt.Year );

//Implementation w/ Example 2
var sc = new SomeClass();
sc.SomeMethod<MyDateClass>( dt => dt.Year );

我难以理解的是第一个示例/实现如何在执行 SomeMethod 时忽略 TProperty 泛型类型,但第二个示例/实现不能以及如何在 T 之间建立隐式关系以及示例/实现 1 中的 TProperty。

解决方案 更改示例 2 中方法的签名如下:

void SomeMethod<T>( Expression<Func<T,Object>> expression ){ ... }

虽然这将允许在表达式中使用任意对象,因此它确实允许如实现 2 中所述的属性连接。

【问题讨论】:

    标签: c# .net generics lambda


    【解决方案1】:

    您允许编译器推断类型参数。这适用于案例 1:

    class SomeClass<T> where T : class {
        void SomeMethod<TProperty>( Expression<Func<T,TProperty>> expression ){ ... }
    }
    

    因为你首先声明了一个实例:

    var sc = new SomeClass<MyDateClass>();
    

    告诉编译器T is MyDateClass。然后,当你调用方法时:

    sc.SomeMethod( dt => dt.Year );
    

    编译器知道T is MyDateClass,所以T.Year 必须是一个int。这些信息足以将SomeMethod 解析为SomeMethod&lt;MyDateClass, int&gt;

    然而,您的第二个示例是明确说明类型参数 - 告诉编译器推断它:

    sc.SomeMethod<MyDateClass>( dt => dt.Year );
    

    不幸的是,它试图用一个类型参数(&lt;MyDateClass&gt; 位)调用SomeMethod。由于那不存在,它会抱怨并说您需要 2 个类型参数。

    如果您像第一个示例一样调用它:

    sc.SomeMethod( dt => dt.Year );
    

    编译器会抱怨,告诉您它无法推断类型参数 - 根本没有足够的信息来确定 dt 是什么。因此,您可以明确声明 both 类型参数:

    sc.SomeMethod<MyDateClass, int>( dt => dt.Year );
    

    或者,告诉编译器 dt 是什么(这是您在第一个示例中通过新 SomeClass&lt;MyDateClass&gt; 所做的):

    sc.SomeMethod((MyDateClass dt) => dt.Year );
    

    编辑和更新:

    编译器在第一个示例中如此有效地执行魔术?假设 TProperty 应该是 int 因为 .Year 是 int?这将回答我的问题,但不能说它令人满意。

    并不是真正的魔法,而是一系列的小步骤。举例说明:

    1. scSomeClass&lt;MyDateClass&gt; 类型,使得 T = MyDateClass
    2. SomeMethod 使用参数dt =&gt; dt.Year 调用。
    3. dt 必须是 T(这就是 SomeMethod 的定义方式),对于 sc 实例,它必须MyDateClass
    4. dt.Year 必须是一个 int,因为 MyDateClass.Year 被声明为一个 int。
    5. 由于dt =&gt; dt.Year总是返回一个int,expression的返回必须是int。因此TProperty = int
    6. 这使得参数expression 变为SomeMethodExpression&lt;Func&lt;MyDateClass,int&gt;&gt;。回想一下T = MyDateClass(第 1 步)和TProperty = int(第 5 步)。
    7. 现在我们已经计算出所有类型参数,使得SomeMethod&lt;T, TProperty&gt; = SomeMethod&lt;MyDateClass, int&gt;

    [B]但是在类级别指定 T 和在方法级别指定有什么区别? SomeClass&lt;SomeType&gt;SomeMethod&lt;SomeType&gt;... 在这两种情况下,T 都是已知的,不是吗?

    是的,T 在这两种情况下都是已知的。问题是SomeMethod&lt;SomeType&gt; 调用的方法只有一个类型参数。在示例 2 中,有 2 个类型参数。您可以让编译器为该方法推断 所有 类型参数,或 none 个。你不能只传递 1 并让它推断另一个 (TProperty)。所以,如果你要显式声明T,那么你也必须显式声明TProperty

    【讨论】:

    • 编译器在第一个示例中如此有效地执行魔术?假设 TProperty 应该是 int 因为 .Year 是 int?这将回答我的问题,但不能说它令人满意。 :)
    • ...我认为灯刚刚熄灭。 class 和 method 之间的区别在于 class 意味着 T 的实例存在于上下文中,而 method 不存在?因此编译器的假设?
    • 不知道你为什么不满意,但静态类型推断并不神奇,它有明确的规则,是 C# 中的一个新特性。
    • @Ikarii - 我不确定你在暗示什么。它不是上下文,T 在第一个示例中是已知的,因为您必须在创建类时指定它。您期望它在第二个中被推断出来。
    • 对...但是在类级别指定 T 和在方法级别指定有什么区别? SomeClass vs. SomeMethod...在这两种情况下 T 都是已知的,不是吗?
    【解决方案2】:

    首先,您的第一个示例不正确,并且不会按照给定的方式编译。除了方法上缺少的public 之外,您还定义了两次T - 一次在类上,一次在方法上。这本身并不是错误(尽管您会收到警告),但是在编译方法调用时,您会收到与您描述的错误相同的错误,例如 #2。所以,我假设实际的代码是这样的:

    //Example 1  
    class SomeClass<T> where T : class
    {
      public void SomeMethod<TProperty>(Expression<Func<T,TProperty>> expression) {}
    }
    

    此外,在您对SomeMethod 的调用中,您的两个示例都“忽略”(即推断)TProperty 的实际类型。您的第二个示例明确指定了T 的类型,是的,但不是TProperty

    在第一个示例中也没有建立隐式关系。当您使用T=MyDateClass 实例化SomeClass&lt;&gt; 时,该实例化的SomeMethod 的签名有效地变为:

    void SomeMethod<TProperty>(Expression<Func<MyDateClass, TProperty>> expression)
    

    所以此时 lambda 的参数类型是已知的,因此您不必指定它。表达式 lambda 的返回类型被推断为 =&gt; 右侧的表达式类型,这将是 TProperty 的推断类型。

    在第二个例子中,因为T在实例化类时没有显式指定,并且由于无法从方法参数中推断出来,所以必须显式指定。指定后,TProperty 的推断方式与示例 #1 完全相同。

    【讨论】:

    • 我不明白你的推断评论是什么意思。您是说这两个示例都不应该工作,因为示例中 TProperty 的类型是未知的?关于语法错误,是的,有罪 - 我在发布之前没有尝试编译这些示例。
    • “推断”的意思是“自动找出类型”。在这种情况下,它是这样的:T 的类型是已知的 -> Func&lt;T1,TResult&gt; 中的 T1 是已知的 -> dt 的类型是已知的 -> lambda 的返回类型被推断为表达式的类型 dt.Year (一旦我们知道dt 的类型,我们显然知道dt.Year 的类型-> Func&lt;T1,TResult&gt; 中的TResult 是已知的-> TProperty 是已知的。
    • 混淆不在于理解推断的含义,而是在类级别与方法级别定义 MyDateClass 有何不同。 IE; SomeClass, SomeMethod... 在这两种情况下,T1 都是已知的,那么为什么 TProperty 的类型是在第一个示例中推断的,而不是第二个示例?
    • 哦,我现在明白了。如果您的示例 #2 完全按照给定,编译器错误仅仅是因为您没有指定 TProperty。这不是因为它无法推断,而是因为与 C++ 不同,C# 不允许您在方法调用中显式指定某些类型参数,而忽略其他类型参数。您可以忽略所有这些(并让它们被推断),或者您指定所有。
    • 没错。 :) 对不起,如果我不清楚。我试图简化示例,但我可能通过使用临时代码添加编译器错误来混淆问题。道歉。
    【解决方案3】:

    请查看Eric Lippert 的帖子,而不是输入具体信息。我想它会回答你想问的问题。

    我知道它很长而且做作,但我认为这是回答你的最佳选择。

    【讨论】:

      【解决方案4】:

      据我所知,问题只是 DateTime 不是一个类...您将 T 作为 DateTime 传递(无论是隐式还是显式)。

      在第一个示例中,您有两个名为T 的类型参数可能有点令人困惑——一个在类型上,一个在方法上。它们实际上是完全分开的......为了相同,将第一个示例重写为:

      //Example 1  
      class SomeClass<T> where T : class
      {
          void SomeMethod<TProperty>( Expression<Func<T,TProperty>> expression ){ ... }
      }
      

      现在是一样的T : class

      【讨论】:

      • 对不起的例子......显然 DateTime 是一个结构。对我的示例进行了编辑,希望能更清楚地说明问题。假设对象实际上是一个具有属性的类而不是结构。
      • 虽然这是真的,当然也可以这样写——我对 TProperty 实际上与 T 相关的连接位置感兴趣,因此在表达式中允许 p => p.Property没有定义 TProperty 的泛型类型。更重要的是,我如何使用不在泛型类中的方法完成同样的事情。我说得有道理吗? :)
      猜你喜欢
      • 1970-01-01
      • 2014-05-16
      • 1970-01-01
      • 2011-11-23
      • 1970-01-01
      • 1970-01-01
      • 2010-12-28
      • 2023-03-10
      • 1970-01-01
      相关资源
      最近更新 更多