IEnumerable<dynamic> 上的 LINQ Select 编译时错误

Compile-time error with LINQ Select on IEnumerable<dynamic>

重大更新请往下看!


我有一些这样的代码:

void Test(IEnumerable x)
{
  var dynX = x.Cast<dynamic>();
  var result = dynX.Select(_ => _.Text);
}

在针对 .NET 4.5 的现有库项目中。 VS2015 的 IntelliSense 强调 Text 部分,抱怨:'object' 不包含 'Text'...

的定义

果然,编译失败

error CS1061: 'object' does not contain a definition for 'Text' and no extension method 'Text' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)

此消息总是显示 'object',即使我将演员表更改为 .Cast<IAsyncResult>() 或诸如此类的东西。当我将鼠标悬停在 lambda 参数上时,工具提示显示它的类型为 IColumn(存在但不相关)。同样,无论我投射到什么类型。

然而,当我将鼠标悬停在 Select() 方法上时,它正确地将参数显示为 Func<dynamic, dynamic>。如果我明确指定 lambda 参数类型,它会编译。如果我在 Select() 上明确指定类型参数,它也可以工作。

LINQ 与 dynamic 的其他用法正在运行。当我将此方法复制到解决方案中的另一个(现有)项目时,它也会编译。当我将它复制到同一项目中的另一个文件时,它无法编译。

它也可以用 VS2013 编译。

我的所有同事也出现了同样的错误,在 Windows 8.1 和 Windows 10 中。

也许这是类型推断的一些奇怪问题...?

我尝试过但没有帮助的事情:


更新

好吧,我设法创建了一个独立的最小失败示例:

static class Program
{
    static void Main(string[] args)
    {
        IEnumerable x = new object[0];
        IEnumerable<dynamic> dynX = x.Cast<dynamic>();

        // CS1061 'object' does not contain a definition for 'Text'...
        // var tooltip shows IColumn instead of IEnumerable<dynamic>
        var result = dynX.Select(_ => _.Text);
    }

    public static IColumn Select<TResult>(this IColumn source, Func<object, TResult> selector)
    {
        throw new NotImplementedException();
    }
}

public interface IColumn { }

在我看来,这清楚地表明 VS2015/the 新编译器版本解析扩展方法的方式存在严重错误。


以下只是松散相关,主要是关于误导性错误消息。我决定保留它以免混淆评论。

更糟糕的是,即使 IEnumerableobject 都不可能有扩展方法 Select():

,它们也会失败并出现相同的错误
// CS1061 'object' does not contain a definition for 'Text'
// var tooltip shows IColumn
var result2 = x.Select(_ => _.Text);

object o = new object();   
// CS1061 'object' does not contain a definition for 'Text'
// var tooltip shows IColumn
var result3 = o.Select(_ => _.Text);

附录

此问题现已在 Roslyn bug tracker 上进行跟踪。

你可以用这个

 static void Main(string[] args)
    {
        //IEnumerable x = new object[0];
        //var result2 = x.Select(_ => _.Text); 
        //Compile time Error "Enumerable" does not contain a definition for  'Select' and no extension method
       // because IEnumerable is not a generic class

        IEnumerable<object> x = new object[0];
        var result2 = x.Select(_ => _.Text);        
    }

不能说为什么它在一个 VS 而不是另一个 VS 中工作,但这就是我会做的

重命名。

public static IColumn Select<TResult>(this IColumn source, Func<object, TResult> selector)
    {
        throw new NotImplementedException();
    }

"Select" 是其他公共库的方法名称的构建。 所以我强烈建议您将其重命名为 "AppColumnSelectText" 之类的名称或其他名称,但不是 "Select".

然后改变

public interface IColumn { }

public interface IColumn 
{
    string Text {get; set;}
}

然后实施它

public class MyClass : IColumn
{
   public string Text { get; set;}
}

然后将您的动态转换为 IColumn,假设它来自 MyClass class 类型

var columns = fields.Select(a => new {a as IColumn}).ToList();

那你就可以了

var result3 = columns.AppColumnSelectText(x => x.Text);

我知道这可能不是您想要的,但编程更清晰,您可以存档相同的结果。

更新

请阅读下面的评论并希望这能描绘出更好的画面。

public static class Test
{
    public static IColumn Select<TResult>(this IColumn source, Func<object, TResult> selector)
    {
        throw new NotImplementedException();
    }

    public static IColumn SelectOtherColumn<TResult>(this IColumn source, Func<IColumn, TResult> selector)
    {
        throw new NotImplementedException();
    }
}

public interface IColumn
{
    string Text { get; set; }
}

public class Program
{
    private static void Main(string[] args)
    {
        IEnumerable ojb = new object[0];
        IEnumerable<dynamic> dynX = ojb.Cast<dynamic>();

        // CS1061 'object' does not contain a definition for 'Text'...
        // var tooltip shows IColumn instead of IEnumerable<dynamic>

        //NB this is the System.Linq.Select
        var result = dynX.Select(x => x.Text);

        var xzy = dynX as IColumn;
        //converstion here will probably FAIL so this makes this pointless.


        //here the compliter complains as the Type object has no Text Prop as it was not sepcified anywhere
        var theThingyouwant1 = xzy.Select(x => x.Text);

        //here you are OK as the complier can infer something
        var theThingyouwant2 = xzy.SelectOtherColumn(x => x.Text);
    }

}

更新 除此之外...请参见下面的插图 perposes

public class MyType
{
    public string  Text { get; set; }
    public string  SomethingEsle { get; set; }
}

public class Program
{
    private static void Main(string[] args)
    {
        List<MyType> ojb = new List<MyType>();
        ojb.Add(new MyType {Text = "OMG", SomethingEsle = "cat"});

        //dynX is a dynamic...
        var dynX = ojb.Select(x => new {x.Text, x.SomethingEsle}).ToList();

        //NB this is the System.Linq.Select
        //this now works as the complier can determine that there is a property Text
        var result = dynX.Select(x => x.Text).ToList();

    }
}

您可以使用您的 Select...但它只适用于实现 IColumn 的东西,这就是为什么我发现很难看出它如何工作的原因。

这只是一个案例,其中 c# 使用鸭子类型的形式来允许 LINQ 适用于任何类型。

我将从一个简单的例子开始。

如果我定义 class Foo 行:

public class Foo
{
    public int Bar;
    public int Select(Func<Foo, int> map)
    {
        return map(this);
    }
}

然后我可以写这段代码:

Foo foo = new Foo() { Bar = 42 };

int query =
    from f in foo
    select f.Bar;

我得到一个适用于 Foo(不是 IEnumerable<T>)类型的 LINQ 版本,returns 和 int(不是 IEnumerable<R>)。

所有的 LINQ 运算符都可以用这种方式专门定义。

上面的例子也可以这样写:

public class Foo
{
    public int Bar;
}

public static class Ex
{
    public static int Select(this Foo source, Func<Foo, int> selector)
    {
        return selector(source);
    }
}

现在它开始看起来像您的代码了。

因此,如果我们进行此更改:

public class Foo
{
}

public static class Ex
{
    public static IColumn Select<TResult>(this IColumn source, Func<object, TResult> selector)
    {
        throw new NotImplementedException();
    }
}

public interface IColumn { }

然后此代码失败并出现与您相同的错误:

IEnumerable<dynamic> foo = new [] { new Foo() };

var query =
    from f in foo
    select f.Text;

如果我随后进行此更改:

public static class Ex
{
    public static IColumn Select<TSource, TResult>(this IColumn source, Func<TSource, TResult> selector)
    {
        throw new NotImplementedException();
    }
}

代码现在报告 "RuntimeBinderException: 'Foo' does not contain a definition for 'Text'"。

我们最后所做的是显示编译器尝试使用您的 Select 方法实现 LINQ Select 运算符。然后它会尝试查看它是否可以使用定义的 Select 方法作为所呈现类型的重载,并且由于 dynamic 可以是 "cast" 到 IColumn 它说你的 Select 方法是最好的重载。此外,由于编译器只会搜索当前 class 中的候选项,因此它不会继续查找标准的 LINQ 运算符。

当然,然后编译器使用您的重载,将 dynamic 强制转换为 object,然后尝试找到 .Text 属性。它当然不能,所以它会报告你的错误。

嗯,看到the bug report解决了好久,总结一下:

这是一个错误,编译器没有按应有的方式应用 dynamic 标志,导致它变成 object。该错误已修复。不知道什么时候能在VS2015中使用,也许其他人可以提供那个信息。

这可能触发了重载解析机制中的一些问题,导致误导性的错误消息和工具提示内容。