LINQ在IEnumerable<dynamic>上使用Select时出现编译时错误

22

请往下看以获取重要的更新内容!


我有一些像这样的代码:

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

在一个针对.NET 4.5的现有库项目中,VS2015的智能感知会在Text部分下划线,并抱怨:“object”不包含“Text”的定义...

确实,编译失败了。

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

这个消息总是说“object”,即使我将其更改为.Cast<IAsyncResult>()或其他内容。当我将光标悬停在lambda参数上时,工具提示显示它是IColumn类型(它存在但无关)。同样,无论我转换成什么类型。

但是,当我将光标悬停在Select()方法上时,它正确地显示参数为Func<dynamic, dynamic>。如果我明确指定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/新编译器版本在解决扩展方法时存在严重的错误。


以下内容与主题关系不大,主要是关于误导性错误消息的。 我决定保留它,以免让评论变得混乱。

更糟糕的是,即使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追踪器上得到跟踪。


这个项目使用哪个框架? - Sarvesh Mishra
1
@MarcinJuraszek 感谢您的提示,我终于成功创建了一个最小化的示例。如果其他人能够测试并确认这确实是一个错误,我会很高兴的。 - Daniel B
1
我认为你的问题可能与这个有关:https://github.com/dotnet/roslyn/issues/4160。我在VS2015和VS2013中测试了你的示例。在VS2013中,`result`可以工作,但`result2`和`result3`都失败了。在VS2015中,它们全部失败,但是我从var提示中得到了不同的结果。var提示显示它是一个`object`而不是`IColumn`。当我按照Roslyn问题页面上的建议将`Func<object, TResult> selector更改为Func<dynamic, TResult> selector`时,我设法在VS2015上编译它。 - Ringil
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Faizan Khan
这确实看起来像是一个Bug。编译器在这里完全混淆了。 - Faizan Khan
显示剩余10条评论
4个回答

1
无法解释为什么它在一个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类类型。
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的内容,这就是为什么我很难看到它如何起作用的原因。

看了那段代码,我会预期你遇到的错误...所以我不知道该怎么帮助你...因为你似乎已经知道了。所以你不是想要一个能工作的代码,而是想知道为什么它在一个VS中可以工作,而在另一个VS中却不能...抱歉,我无法帮忙,我预期它应该在两个VS中都失败...所以我很困惑。 - Seabizkit
请将以下与编程有关的内容从英语翻译成中文。仅返回已翻译的文本:请注意代码更改,查看我在原始答案中提供的更新。 - Seabizkit
那不是一个动态对象,而是一个匿名类型。两者是完全不同的东西。dynamic会在运行时解析所有调用。 - Daniel B
让我们在聊天中继续这个讨论 - Seabizkit
你有读过 https://dev59.com/N1zUa4cB1Zd3GeqP6L4n?rq=1 吗?它可能会有帮助。 - Seabizkit
显示剩余5条评论

0

好的,既然错误报告已经解决了一段时间,让我们总结一下:

这是一个bug,编译器没有像应该的那样应用dynamic标志,导致它变成了一个object。该漏洞已经修复。我不知道它何时会在VS2015中可用,也许其他人可以提供这方面的信息。

这可能触发了重载分辨率机制中的某些怪癖,导致误导性的错误消息和工具提示内容。


0

这只是一个简单的情况,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<T>)并返回int(而不是IEnumerable<R>)的LINQ版本。

所有的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上失败。你想说什么? - Daniel B
编译器试图从任何名为 Select 的方法实现 LINQ select 操作符,该操作符实现了多种支持的签名,这就导致了这种奇怪的行为。尝试更改方法的名称,行为将会发生变化。 - Enigmativity
这并不是“任何名为Select的方法”。它是一个实例或扩展方法 Select(Func<,>)。然而,我的Select方法在IEnumerable<dynamic>中不是一个扩展方法。因此,在这里它根本不适用。此外,我没有使用查询语法。 - Daniel B
@DanielB - 我说的是“任何实现多种支持签名的Select方法”,而不仅仅是“任何名为Select的方法”。 - Enigmativity
这并不适用,因为IColumnIEnumerable<T>没有任何关系。无论您是使用LINQ查询语法还是常规扩展方法,编译器都不应该选择它。仅仅因为该错误取决于Func<,>的类型参数,并不意味着它适用。 - Daniel B
显示剩余4条评论

0

你可以使用这个

 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#编译器在解析扩展方法时行为发生变化的。 - Daniel B

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