LINQ Union 不同类型 - 动态转换为接口?

3

我有10个表格,它们在LINQ中以不同的类型呈现,但共享完全相同的属性。当我尝试在它们上运行联合时,编译器告诉我:

"Argument 2: cannot convert from 'System.Collections.IEnumerable' to 'System.Collections.Generic.IEnumerable<LINQPad.User.TelJun2011>'"

代码看起来像这样
var jul = (from n in TelJul2011s select n);

var jun = (from p in TelJun2011s select p);

jun.Union(jul).Dump();

我已经进行了研究并且理解了不同类型之间不能执行联合操作,我也明白如果匿名类型共享相同属性,则可以对其执行联合操作。但是这个选项对我来说行不通,因为我需要从所有表中获取所有属性,并且不想为每个变量输入相同的匿名类型10次。我希望编译器基于所有属性都相同的事实来推断它们都是相同的类型。
我已经尝试过使用AsQueryable()类型函数和"as"关键字将其转换为IEnumberable、Iqueryable、Datatable等,但似乎都不起作用。
我想知道是否有一种通过动态转换为父类型来实现的方法。我无法编辑类的初始声明,因此无法在它们上实现公共接口进行转换。但是否有一种方法可以在使用时将类型向下转换为公共接口,而无需从每种类型到父接口编写转换?
感谢任何建议!

1
据我所知,你已经是唯一的选择了...并且说你不能使用它们...所以基本上在这些限制条件下,答案是否定的。 - Yahia
是的,就是这样。正确的解决方案应该是有一个特定的非平板类型(非匿名)并选择进入这个类型。显然,源原因是破损的数据库设计。 - TomTom
不是破碎的数据库设计。我正在评估使用linq作为特别分析解决方案来查询多个来源的潜力。合并不同来源是一种基本的SQL功能,通常用于分析,即使我同意在生产或实际应用中这将是不良的做法。 - analystic
如果您正在使用EntityFramework,则类将被创建为“partial”,然后您可以创建另一个具有相同部分类的.cs文件,强制类实现自定义接口,该接口将具有所需的属性。 - Arturo Torres Sánchez
3个回答

9

Union的结果将是一个IEnumerable<Xxx>,但在这种情况下,您需要指定Xxx是什么。如果类型TelJun2011TelJul2011不是结构体(值类型),您可以像这样利用IEnumerable<out T>的协变性:

jun.Union<object>(jul)

这是可行的,因为TelJun2011TelJul2011都是object,然后通过协变性,IEnumerable<TelJun2011>IEnumerable<TelJul2011>都是IEnumerable<object>
当然,object并不具备TelJun2011TelJul2011共有的所有属性。如果这两种类型有一个更有用的公共基类或实现了一个公共接口,那就更好了,因为你可以这样说:
jun.Union<ITelMonth>(jul)

其中ITelMonth是包含您所需公共属性的某种类型。


这个答案很棒...在阅读这篇文章之前,我并没有深入了解协变性,因为我不知道它是如此强大的新语言特性。再次感谢Jeppe分享您的知识! - analystic

0
如果您正在使用EntityFramework(或任何其他框架),则自动生成的类将被标记为“partial”,例如以下内容:

/Project/Data/TelJun2011.cs

namespace Project.Data
{
    using System;
    using System.Collections.Generic;

    public partial class TelJun2011
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
    }
}

/Project/Data/TelJul2011.cs

namespace Project.Data
{
    using System;
    using System.Collections.Generic;

    public partial class TelJul2011
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
    }
}

partial 的意思是你可以为同一个类创建另一个文件。生成的类不实现接口,但你可以很容易地让它们实现你自定义的接口,像这样:

/Project/Data/ITelMonth.cs

namespace Project.Data
{
    public interface ITelMonth
    {
        int Id { get; set; }
        string Name { get; set; }
        string Description { get; set; }
    }
}

/Project/Data/Partial/TelJun2011.cs

namespace Project.Data
{
    public partial class TelJun2011 : ITelMonth { }
}

/Project/Data/Partial/TelJul2011.cs

namespace Project.Data
{
    public partial class TelJul2011 : ITelMonth { }
}

在正确定义了接口之后,我们可以简单地这样做:

var jul = (from n in TelJul2011s select (ITelMonth)n);

var jun = (from p in TelJun2011s select (ITelMonth)p);

var bimester = jun.Union(jul);

而且你甚至可以像这样访问常见的属性:

foreach (var e in bimester)
{
    e.Id.Dump();
}

0
“问题”在于C#的静态类型系统不允许您尝试实现的内容。在将它们合并之前,您必须将对象转换为基本类型。这意味着如果两个对象只有System.Object共同点,则必须将其强制转换为object或(对于.NET 4.0)dynamic
后者将强制进行动态类型检查:
class A
{
    public int Integer { get; set; }
}

class B
{
    public int Integer { get; set; }
}

class Program
{
    public static void main(string[] args)
    {
        var a = new[] { new A { Integer = 5 }, new A { Integer = 6 }, new A { Integer = 7 } };
        var b = new[] { new B { Integer = 1 }, new B { Integer = 2 }, new B { Integer = 3 } };

        var u = a.Cast<dynamic>().Union(b).ToArray();

        var i1 = u[0].Integer;
        var i2 = u[1].Integer;
        var i3 = u[2].Integer;
        var i4 = u[3].Integer;
        var i5 = u[4].Integer;
        var i6 = u[5].Integer;
    }
}

在我看来,这不是一个理想的解决方案,但这可能会对你有所帮助。

我认为您不需要使用Cast方法。IEnumerable<out T>的协变性是否允许您只需编写:var u = a.Union<dynamic>(b); - Jeppe Stig Nielsen

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