【问题标题】:Compile-time error with LINQ Select on IEnumerable<dynamic>IEnumerable<dynamic> 上的 LINQ Select 的编译时错误
【发布时间】:2015-07-29 12:14:21
【问题描述】:

请进一步查看重大更新!


我有一些这样的代码:

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

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

果然编译失败了

错误 CS1061:“object”不包含“Text”的定义,并且找不到接受“object”类型的第一个参数的扩展方法“Text”(您是否缺少 using 指令或程序集引用?)

此消息始终显示'object',即使我将演员表更改为.Cast&lt;IAsyncResult&gt;() 或诸如此类。当我悬停 lambda 参数时,工具提示显示它的类型为 IColumn(存在但不相关)。同样,无论我转换为哪种类型。

但是,当我将Select() 方法悬停时,它正确地将参数显示为Func&lt;dynamic, dynamic&gt;。如果我明确指定 lambda 参数类型,它会编译。如果我在Select() 上明确指定类型参数,它也可以工作。

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

它也可以用 VS2013 编译。

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

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

我尝试过但无济于事的事情:

  • 创建一个新的 .NET 4.5 库项目并重新添加文件和缺少的引用
  • 比较(原始)项目文件——除了元素排序之外没有区别

更新

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

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/新编译器版本在解析扩展方法方面存在严重错误。


以下内容只是松散相关,主要是关于误导性错误消息。我决定保留它,以免让 cmets 感到困惑。

更糟糕的是,即使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 上进行跟踪。

【问题讨论】:

  • VS 2010 没有给出任何此类编译时错误!
  • 这个项目使用了哪个框架?
  • 我设法在 VS2015 中在 4.5.2 上很好地编译了这个方法。和 4.6.
  • @MarcinJuraszek 感谢您的提示,我终于设法创建了一个最小的示例。如果其他人可以对此进行测试并确认这确实是一个错误,我会很高兴。
  • 我认为您的问题可能与此有关:github.com/dotnet/roslyn/issues/4160。我在 VS2015 和 VS2013 中测试了您的示例。在 VS2013 中,result 有效,但 result2result3 都失败了。在 VS2015 中,它们都失败了,但是我从 var 工具提示中得到了不同的结果。 var 工具提示显示它是object 而不是IColumn。当我按照 Roslyn 问题页面上的建议将 Func&lt;object, TResult&gt; selector 更改为 Func&lt;dynamic, TResult&gt; selector 时,我设法让它在 VS2015 上编译。

标签: c# dynamic extension-methods visual-studio-2015 type-inference


【解决方案1】:

无法说明为什么它在一个 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 类类型

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);
    }

}

更新 除此之外......请参阅下面的说明

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 的东西,这就是为什么我很难看到它是如何工作的。

【讨论】:

  • 也跑题了。我不是在问如何解决这个问题,因为那是微不足道的。我们的方法和Enumerable.Select很像,为什么要重命名呢?另外,你的命名意识很糟糕。 ;)
  • 如果您要覆盖 Select... 然后确定。但就我个人而言,我不会这样做,因为我想保持 Select 原样(原样)......并创建我自己的略有不同的 Select(无论如何取决于偏好)。您的代码将如何了解属性 Text。当您在 Select... 时,lambda 表达式 linq 将如何在...编译时了解 Text。你甚至没有指定动态的属性......所以我看不出它是如何工作的......
  • PS 命名很糟糕......因为我不知道......这是什么意思......只有你做......这就是为什么我说......把它命名为你喜欢;-)
  • 查看该代码,我预计您会遇到错误……所以我不知道如何提供帮助……因为您似乎知道这一点。因此,您不想要有效的代码..,您想知道为什么它在一个VS中有效,而在另一个VS中无效.... Sozs无能为力,我希望它在两者中都失败...所以我不知所措。
  • 我没有覆盖Select。它是IColumn 的扩展方法,与IEnumerable&lt;T&gt; 没有任何关系。因此,根本不可能发生此错误。当然,除非编译器有错误。如果您对 dynamic 的工作原理感到困惑,您应该研究一下。
【解决方案2】:

你可以用这个

 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);        
    }

【讨论】:

  • 我不明白这与我的问题有什么关系,这是关于解析扩展方法时 C# 编译器的行为改变。
【解决方案3】:

这只是 c# 使用一种鸭子类型来允许 LINQ 用于任何类型的情况。

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

如果我定义一个类 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&lt;T&gt;)的 LINQ 版本,它返回一个 int(不是 IEnumerable&lt;R&gt;)。

所有的 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' 不包含 'Text' 的定义”。

我们最终完成的是显示编译器尝试使用您的 Select 方法实现 LINQ Select 运算符。然后它会尝试查看它是否可以使用定义的Select 方法作为所呈现类型的重载,并且由于dynamic 可以“强制转换”为IColumn,它说您的Select 方法是最好的重载。此外,由于编译器只会搜索当前类中的候选对象,因此不会继续查找标准 LINQ 运算符。

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

【讨论】:

  • 我看不出 LINQ 查询语法示例与我的问题有何关联。它的行为方式完全相同:它在 VS2013 上编译,在 VS2015 上失败。你想说什么?
  • 编译器尝试从任何称为Select 的方法实现LINQ select 运算符,该方法实现了各种受支持的签名,这给您带来了这种奇怪的行为。尝试更改方法的名称并更改行为。
  • 这不是“任何称为Select的方法”。这是一个实例或扩展方法Select(Func&lt;,&gt;)。但是,我的Select 方法不是IEnumerable&lt;dynamic&gt; 中的扩展方法。因此,它在这里根本不适用。另外,我没有使用查询语法。
  • @DanielB - 适用于此处。当我将您的代码签名从 public static IColumn Select&lt;TResult&gt;(this IColumn source, Func&lt;object, TResult&gt; selector) 更改为 public static IColumn Select&lt;TSource, TResult&gt;(this IColumn source, Func&lt;TSource, TResult&gt; selector) 时,您示例中的代码编译得很好。原始代码显然失败了,因为您的 dynX.Select(_ =&gt; _.Text) 正在调用您的 Select 方法。
  • @DanielB - 我确实说过这是“任何称为 Select 的方法实现了各种受支持的签名”,而不仅仅是“任何称为 Select 的方法”。
【解决方案4】:

好吧,看看the bug report是怎么解决了好久的,总结一下吧:

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

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

【讨论】:

    猜你喜欢
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    • 2015-04-11
    • 1970-01-01
    • 1970-01-01
    • 2011-03-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多