【问题标题】:Looking for non-type-specific method of handling Generic Collections in c#寻找在 c# 中处理通用集合的非类型特定方法
【发布时间】:2012-10-25 21:27:06
【问题描述】:

我的情况是这样的。我需要在多种不同类型的对象上运行一些验证和按摩类型代码,但是为了清洁(和代码重用),我想让这个验证的所有调用看起来基本相同,无论对象如何。我正在尝试通过重载来解决这个问题,在我得到通用集合对象之前效果很好。

下面的例子应该澄清我在这里所说的:

private string DoStuff(string tmp) { ... }

private ObjectA DoStuff(ObjectA tmp) { ... }

private ObjectB DoStuff(ObjectB tmp) { ... }

...

private Collection<ObjectA> DoStuff(Collection<ObjectA> tmp) {
    foreach (ObjectA obj in tmp) if (DoStuff(obj) == null) tmp.Remove(obj);
    if (tmp.Count == 0) return null;
    return tmp;
}

private Collection<Object> DoStuff(Collection<ObjectB> tmp) {
    foreach (ObjectB obj in tmp) if (DoStuff(obj) == null) tmp.Remove(obj);
    if (tmp.Count == 0) return null;
    return tmp;
}

...

这似乎是一种真正的浪费,因为我必须为每个不同的Collection&lt;T&gt; 类型复制完全相同的代码。我想创建一个处理任何Collection&lt;T&gt;DoStuff 实例,而不是为每个实例单独创建一个。

我曾尝试使用ICollection,但这有两个问题:第一,ICollection 没有暴露.Remove 方法,并且我无法编写foreach 循环,因为我不知道类型列表中的对象。使用更通用的东西,比如object,是行不通的,因为我没有接受object 的方法DoStuff - 我需要它为实际对象调用适当的方法。编写一个 DoStuff 方法,该方法采用 object 并执行某种庞大的 if 语句列表来选择正确的方法并适当地进行转换,这违背了摆脱冗余代码的整个想法——我不妨复制并粘贴所有Collection&lt;T&gt; 方法。

我尝试过使用通用的DoStuff&lt;T&gt; 方法,但这在foreach 循环中存在同样的问题。因为我在设计时不知道对象类型,所以编译器不会让我调用DoStuff(obj)

从技术上讲,编译器应该能够在编译时判断需要进行哪个调用,因为这些都是私有方法,并且在调用中传递的对象的具体类型在方法所在的点都是已知的被调用。这些知识似乎并没有出现在此方法调用的后续方法中。

我真的不想在这里使用反射,因为这会使代码比复制和粘贴所有Collection&lt;T&gt; 方法更加复杂,并且会降低性能。有什么想法吗?

---编辑 1--- 我意识到我的通用方法引用没有正确显示,因为我没有使用尖括号的 html 代码。现在应该解决这个问题。

---编辑 2--- 根据下面的回复,我将Collection&lt;T&gt; 方法更改为如下所示:

private Collection<T> DoStuff<T>(Collection<T> tmp) {
    for (int i = tmp.Count - 1; i >= 0; i--) if (DoStuff(tmp[i]) == null) tmp.RemoveAt(i);
    if (tmp.Count == 0) return null;
    return tmp;
}

但是,这仍然不起作用,因为当我调用 DoStuff(tmp[i]) 时,编译器无法确定要调用哪个重载方法。

【问题讨论】:

    标签: c# generics collections overloading generic-collections


    【解决方案1】:

    您需要将要调用的方法作为参数传递给泛型方法。这样,重载决议发生在编译器知道期望什么类型的地方。

    或者,您需要将每个项目的 DoStuff 方法设为通用(或对象)以支持集合中任何可能的项目。

    (我还将 RemoveItem 调用与第一个循环分开,这样它就不会尝试从被迭代的同一个列表中删除项目。)

    private Collection<T> DoStuff<T>(Collection<T> tmp, Func<T, T> stuffDoer)
    {
        var removeList = tmp
            .Select(v => stuffDoer(v))
            .Where(v => v == null)
            .ToList();
    
        foreach (var removeItem in removeList) tmp.Remove(removeItem);
    
        if (tmp.Count == 0) return null;
        return tmp;
    }
    
    private class ObjectA { }
    private class ObjectB { }
    
    private string DoStuff(string tmp) { return tmp; }
    
    private ObjectA DoStuff(ObjectA tmp) { return tmp; }
    
    private ObjectB DoStuff(ObjectB tmp) { return tmp; }
    

    使用此代码调用:

    var x = new Collection<ObjectA>
    {
        new ObjectA(),
        new ObjectA(),
        null
    };
    
    var result = DoStuff(x, DoStuff);
    

    【讨论】:

      【解决方案2】:

      像这样?:

      private Collection DoStuff<T>(Collection tmp) 
      {
          // This will probably assert as you are modifying a collection while looping in it.
          foreach (T obj in tmp) if (DoStuff(obj) == null) tmp.Remove(obj); 
          if (tmp.Count == 0) return null;
          return tmp;
      }
      

      其中 T 是集合中对象的类型。

      请注意,您有一条很可能会断言的行。所以:

      private Collection DoStuff<T>(Collection tmp) 
      {
          // foreach doesn't work if you are modifying the collection.
          // Looping backward with an index, so we never encounter an invalid index.
          for (int i = tmp.Count - 1; i >= 0; i--) if (DoStuff(tmp[i]) == null) tmp.Remove(tmp[i]);
          if (tmp.Count == 0) return null;
          return tmp;
      }
      

      但是在这一点上……既然你不再使用 T 了,为什么要让它通用呢?

      private Collection DoStuff(Collection tmp) 
      {
          // DoStuff can be generic, but you shouldn't need to explicitly pass it a type...
          for (int i = tmp.Count - 1; i >= 0; i--) if (DoStuff(tmp[i]) == null) tmp.Remove(tmp[i]);
          if (tmp.Count == 0) return null;
          return tmp;
      }
      

      【讨论】:

      • 制作Collection Collection&lt;T&gt; 我想你会找到答案的:)
      • @MikeMcCaughan Collection 是不是泛型的?但即使是这样,我也没有任何理由知道它的类型,正如您在我所做的最后一个示例中看到的那样。 DoStuff 很可能是一个泛型。
      • 嗯...好点。编译器必须知道tmp[i] 是什么类型,并且可以转发到正确的重载。
      • 您关于删除在 foreach 中不起作用的观点是一个很好的观点。我会解决的。
      • 更大的问题是 Collection 不是一个对象。您不能让方法返回 Collection。此外,编译器仍然不喜欢 DoStuff(tmp[i]),因为它不知道如何确定要调用哪个 DoStuff。
      猜你喜欢
      • 2013-03-24
      • 1970-01-01
      • 2015-12-04
      • 1970-01-01
      • 2020-10-29
      • 1970-01-01
      • 2022-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多