【问题标题】:One liner for concatenation of two different lists of objects with the same interface一个用于连接具有相同接口的两个不同对象列表的衬垫
【发布时间】:2013-04-11 07:11:30
【问题描述】:

考虑这个简单的代码:

public interface Iinterface { }
public class Foo : Iinterface { }
public class Bar : Iinterface { }
[TestMethod()]
public void Test_Concat()
{
    var bars = new List<Bar>();
    var foos = new List<Foo>();
    // Ok
    IEnumerable<Iinterface> concats1 = bars.Cast<Iinterface>().Concat(foos.Cast<Iinterface>());
    // Compilation error
    IEnumerable<Iinterface> concats2 = bars.Concat(foos);
}

我想将两个列表合并为一行,并在编译时保持类型安全

例如,如果我删除了Foo 类的接口,这仍然会编译,但在运行时会失败:

public interface Iinterface { }
public class Foo { }
public class Bar : Iinterface { }
[TestMethod()]
public void Test_Concat()
{
    var bars = new List<Bar>();
    var foos = new List<Foo>();
    IEnumerable<Iinterface> concats1 = bars.Cast<Iinterface>().Concat(foos.Cast<Iinterface>());
}

如果我使用OfType&lt;T&gt;(),这不会在运行时失败,但我希望它在编译时失败。 我能找到的最好的方法是使用这 3 行代码:

var list = new List<Iinterface>();
list.AddRange(bars);
list.AddRange(foos);

但是对于这么简单的事情,我想找到一个单行,如果可能的话,检索一个 IEnumerable&lt;Iinterface&gt; 而不是 List&lt;Iinterface&gt;
有什么方法可以实现吗?

【问题讨论】:

    标签: c# .net linq generics interface


    【解决方案1】:

    你可以自己写方法吗?

    由于 IEnumerable 是协变的,因此您只需指定基数

    var list= Combine<IInterface>(foos, bars);
    
    private IEnumerable<T> Combine<T>(IEnumerable<T> ListA, IEnumerable<T> ListB)
    {
        foreach (T t in ListA)
            yield return t;
        foreach (T t in ListB)
            yield return t;
    }
    

    虽然你也可以写

    var list= foos.Concat<IInterface>(bars);
    

    【讨论】:

    • 你通过犯错来给出答案!您不调用 Combine 方法,而是调用框架的 Concat 方法,并且它很简单!我只需要将 添加到 Concat 方法。
    • @Scorpi0 这不是错误,我写完才意识到我在重新发明轮子!我在第一个块的顶部调用了 Combine 方法
    【解决方案2】:

    如何制作您的自定义Concat 扩展方法版本:

    public static class MyEnumerable
    {
        public static IEnumerable<T> Concat<T, T1, T2>(this IEnumerable<T1> source, this IEnumerable<T2> other)
            where T1 : T
            where T2 : T
        {
            return source.Cast<T>().Concat(other.Cast<T>());
        }
    }
    

    用法:

    var items = foos.Concat<Iinterface, Foo, Bar>(bars);
    

    它具有编译时安全性,如果FooBar 中的任何一个没有实现Iinterface,则不会编译。

    它还应该支持开箱即用的不同执行,因为使用的 LINQ 方法可以。

    【讨论】:

    • 好主意,可惜编译器无法推断类型。
    【解决方案3】:

    其实concat方法已经做到了我想要的,我只是需要通过指定返回类型来提供一点帮助,因为there is no type inference on return types.

        public interface Iinterface { }
        public class Foo : Iinterface { }
        public class Bar : Iinterface { }
        [TestMethod()]
        public void Test_Concat()
        {
            var bars = new List<Bar>();
            var foos = new List<Foo>();
    
            var list = foos.Concat<Iinterface>(bars);
        }
    

    我太习惯使用推理了,以至于有时我忘记了,编译器需要提示!

    【讨论】:

      【解决方案4】:

      您可以这样做:

      public void Test_Concat2()
      {
          IEnumerable<Iinterface> bars = new List<Bar>();
          IEnumerable<Iinterface> foos = new List<Foo>();
      
          IEnumerable<Iinterface> concats = bars.Concat(foos);
      }
      

      如果您随后更改定义 FooBar 使其不再具有接口,您将收到编译时错误。

      【讨论】:

      • 这不一样 // 编译错误 IEnumerable concats2 = bars.Concat(foos.AsEnumerable()); (对不起,我一小时前把评论放错地方了)。
      【解决方案5】:

      问题似乎出在 Cast 而不是 Concat。声明如下:

      public static IEnumerable<TSource> Concat<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second);
      
      public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source);
      

      在 Concat 中,两个 enunerable 都是强类型的,但 Cast 不限制源类型,因此问题发生在运行时而不是编译时。

      (TResult) s

      的运行时尝试如下的 MyCast 实现失败
      public static IEnumerable<TResult> MyCast<TResult>(this IEnumerable source)
      {
          var result = (from object s in source select (TResult) s).ToList();
          return result.AsEnumerable();
      }
      

      如果像这样在新的 Cast 中实现一点类型检查:

      public static IEnumerable<TResult> MyCast2<TSource, TResult>(this IEnumerable<TSource> source)
          where TSource : TResult
      {
          var result = (from object s in source select (TResult)s).ToList();
          return result.AsEnumerable();
      }
      

      那么调用行需要两个像这样的类型参数

      IEnumerable<Iinterface> concats3 = bars.Concat(foos.MyCast2<Foo, Iinterface>());
      

      并且编译器检测到 Foo 不能转换为 Iinterface。

      当然,在Concat上指定返回类型更简洁。

      【讨论】:

        猜你喜欢
        • 2011-04-07
        • 2021-11-25
        • 1970-01-01
        • 1970-01-01
        • 2022-01-11
        • 2012-01-09
        • 1970-01-01
        • 1970-01-01
        • 2016-01-06
        相关资源
        最近更新 更多