【问题标题】:How to manage this situation by generic programming in C#?如何通过 C# 中的泛型编程来管理这种情况?
【发布时间】:2016-04-30 15:07:08
【问题描述】:

我有下面的代码,它在我的代码中的两个地方实现了我需要的逻辑。代码以通用方式编写。

它有一个缺陷。它不检查给定对象是否已存在于 collectionToBeExtended 中。它只是添加到它,我将有两次相同的项目。

我使用这段代码管理两个集合,一个包含标签,另一个包含链接实体 (POCO)。它们没有相同的父对象,也没有接口。为了管理下面的情况,我不想为他们介绍一个新的通用界面。

为了修复上面的缺陷,我需要在 foreach 中放置自定义逻辑来检查给定对象是否已经是集合的成员。 Tag 和 Entity 对象不同,所以比较有共同点。您可以在下面看到 Tag 实现。

DRY 原则清楚地表明我不应该创建确切的实现,而是应该创建一些通用的解决方案来处理这两种情况,或者第三种和第四种情况。我的第一个想法是,我引入了一个开关,我可以在其中放置基于对象类型的自定义逻辑,但 Martin Fowler 在他的 Refactoring 书中说,switch-cases 是有味道的区域。

我目前的知识和经验不足以解决这个问题,所以非常感谢任何帮助!

通用实现:

private ICollection<T> AddTo<T> ( IEnumerable<T> toBeAdded , ICollection<T> collectionToBeExtended )
    {

        if ( collectionToBeExtended == null )
        {
            collectionToBeExtended = new List<T> ( );
        }

        if ( toBeAdded != null )
        {
            foreach ( T obj in toBeAdded )
            {
                    collectionToBeExtended.Add(obj);
            }
        }

        return collectionToBeExtended;
    }

标签实现:

private ICollection<Tag> AddTo ( IEnumerable<Tag> toBeAdded , ICollection<Tag> collectionToBeExtended )
    {

        if ( collectionToBeExtended == null )
        {
            collectionToBeExtended = new List<Tag> ( );
        }

        if ( toBeAdded != null )
        {
            foreach ( Tag tag in toBeAdded )
            {
                if (!collectionToBeExtended.Any(c => c.Id == tag.Id && c.Name == tag.Name))
                {
                    collectionToBeExtended.Add(tag);
                }
            }
        }

        return collectionToBeExtended;
    }

【问题讨论】:

    标签: c# generics


    【解决方案1】:

    C# 中的所有类型都可以等同于x.Equals(y)。如果你愿意,你可以覆盖这个相等性。

    我会使用IEnumerable&lt;A&gt; 来解决这个问题。如果你之后想要一个ICollection&lt;A&gt;,那么你可以从IEnumerable&lt;A&gt; 构造一个集合。

    collectionToBeExtended.Concat(toBeAdded).Distinct()
    

    【讨论】:

    • +1 我认为这是最好的。请注意,结果还将删除原始 collectionToBeExtended 中的任何重复项,这可能是预期的,也可能不是预期的。 (似乎暗示了这个问题。)
    【解决方案2】:

    我希望这不是矫枉过正,但扩展方法似乎对我有用。希望对你也有帮助。

    public static ICollection<X> AddIf<X>(this ICollection<X> myarray, X val, Func<X, X, bool> check)
        {
            if (myarray.All<X>((c) => check(c, val))) myarray.Add(val);
            return myarray;
        }
    

    这可以用作

    List<string> test = new List<string> { "1", "2", "3", "4", "5" };
    test.AddIf<string>("1", (a, b) => !a.Equals(b)); //this will not be added
    test.AddIf<string>("6", (a, b) => !a.Equals(b)); //this will be added
    

    或者在你的情况下,

    collectionToBeExtended.AddIf((c, tag) => !c.Id.Equals(tag.Id) && !c.Name.Equals(tag.Name))
    

    请确保将扩展方法放在静态类中。

    【讨论】:

      【解决方案3】:

      你可以为Tag实现IEquatable&lt;T&gt;

      public class Tag : IEquatable<Tag>
      {
          public int Id { get; set; }
          public string Name { get; set; }
          public bool Equals(Tag other)
          {
              return Id == other.Id && Name == other.Name;
          }
      
          ...
      }
      

      那么你的代码就是

      private ICollection<T> AddTo ( IEnumerable<T> toBeAdded , ICollection<T> collectionToBeExtended )
      {
      
          if ( collectionToBeExtended == null )
          {
              collectionToBeExtended = new List<T> ( );
          }
      
          if ( toBeAdded != null )
          {
              foreach ( T t in toBeAdded )
              {
                  if (!collectionToBeExtended.Contains(t))
                  {
                      collectionToBeExtended.Add(t);
                  }
              }
          }
      
          return collectionToBeExtended;
      }
      

      您必须为所有想要将它们传递给AddTo 方法的类实现IEquatable&lt;T&gt;。并记住正确实施它。见Is there a complete IEquatable implementation reference?。

      还有另一种选择。只需添加另一个参数来比较两个实体,例如

      private ICollection<T> AddTo<T>(IEnumerable<T> toBeAdded, ICollection<T> collectionToBeExtended, Func<T, T, bool> comparer)
      {
      
          if (collectionToBeExtended == null)
          {
              collectionToBeExtended = new List<T>();
          }
      
          if (toBeAdded != null)
          {
              foreach (T t in toBeAdded)
              {
                  if (!collectionToBeExtended.Any(existingT => comparer(t, existingT))))
                  {
                      collectionToBeExtended.Add(t);
                  }
              }
          }
      
          return collectionToBeExtended;
      }
      

      然后你像AddTo(tags, collection, (t1, t2) =&gt; t1.Id == t2.Id &amp;&amp; t1.Name == t2.Name);一样调用这个方法。如果其中一个集合有大数据,则应考虑性能。

      【讨论】:

        【解决方案4】:

        如果您在Tag 中实现了IEquatable&lt;T&gt; 以及您使用此方法的任何其他类,并向您的方法添加通用约束where T:IEquatable&lt;T&gt;,您现在可以扫描集合中的现有成员并比较它们是否相等。

        但是你只是在复制 HashSet&lt;T&gt; 中已经存在的代码,那么为什么不将该类与构造函数 HashSet&lt;T&gt; Constructor (IEqualityComparer&lt;T&gt;) 一起使用呢?

        【讨论】:

        • 在Entity Framework实体上使用这样的解决方案是否有效?
        • 是的,您可以将IEquatable&lt;T&gt; 添加到EF 实体类(部分或POCO)。
        猜你喜欢
        • 2023-03-25
        • 2021-03-07
        • 1970-01-01
        • 2020-09-26
        • 2019-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多