代码之家  ›  专栏  ›  技术社区  ›  David

如何使用泛型比较C#中X的列表和Y的列表?

  •  4
  • David  · 技术社区  · 14 年前

    我有两个班,X和Y。这两个类具有相同的类似属性,如下所示。

    class X
    {
        public string T1 { get; set; }
        public string T2 { get; set; }
        public string T3 { get; set; }
    }
    
    class Y
    {
        public string T1 { get; set; }
        public string T2 { get; set; }
        public string T3 { get; set; }
    
        public string O1 { get; set; }
    }
    

    我有几百个类似于X和Y的类;类似的结构,我决定为这个问题创建泛型类。

    我有X和Y的列表,我想用T1来比较它们;只有1个属性,以找出两个列表上都存在哪个元素,哪个元素只存在于X上,哪个元素只存在于Y上。

    我该怎么做?

    5 回复  |  直到 14 年前
        1
  •  7
  •   Abel    14 年前

    最好的方法是首先创建一个包含 T1 X Y 从这个界面。现在您可以轻松地创建基于此接口的泛型类或任何助手类。

    或者,您可以使用反射,或者如果您使用C#4.0,您可以使用 dynamic

    或者(2):当您希望“正确”地执行此操作并且希望使用LINQ之类的标准机制比较列表时,应该实现IComparable。您可以将其与泛型结合起来创建类型安全性。

    // the interface, inherit from IComparable
    public interface IX : IComparable<IX>
    {
        string T1 { get; set; }
    }
    
    // create one base class
    class XBase : IX
    {
        public string T1 { get; set; }
        public int CompareTo(IX obj)
        {
            return this.T1.equals(obj.T1);
        }
    }
    
    // inherit all others from base class
    class X : XBase
    {
        public string T2 { get; set; }
        public string T3 { get; set; }
    }
    
    class Y : XBase
    {
        public string T2 { get; set; }
        public string T3 { get; set; }
    
        public strign O1 { get; set; }
    }
    

    还有很多其他的方法。上面最后一种方法的优点是只需为 T1级 CompareTo ,这样可以避免混乱,并使代码更清晰。

        2
  •  4
  •   tster    14 年前

    我很难理解这个问题。但我把它读作“如何根据T1值找出这两个对象列表之间的差异”,然而,就像我说的,这是一个关于实际问题的完全猜测。

    在这里使用linq对您来说是一个很好的开始:

    IEnumerable<string> intersectionT1s = listX.Select(x => x.T1).Intersect(listY.Select(y => y.T1);
    IEnumerable<X> intersection = listX.Where(x => intersectionT1s.Contains(x.T1));
    IEnumerable<X> onlyOnX = listX.Where(x => !listY.Any(y => y.T1 == x.T1));
    

    我只给读者留一个练习。

    public static class ExtensionMethods
    {
        public static IEnumerable<TLeft> IntersectionOn<TLeft, TRight, TField>(this IEnumerable<TLeft> left,
            IEnumerable<TRight> right, Func<TLeft, TField> leftSelector, Func<TRight, TField> rightSelector)
        {
            var intersectionFields = left.Select(leftSelector).Intersect(right.Select(rightSelector));
            return left.Where(x => intersectionFields.Contains(leftSelector(x)));
        }
    }
    

    IEnumerable<X> intersection = listX.IntersectionOn(listY, x => x.T1, y => y.T1);
    
        3
  •  3
  •   Ben Voigt    14 年前

    如果你想要一个可重复使用的答案,而不是特定于 class X class Y ,你需要反思。看一看 Type.GetProperty PropertyInfo.GetGetMethod .

    编辑:我似乎得到了不熟悉反射的人的反对票,所以我将添加一些示例源代码:

    static class PropertyGetter<X>
    {
      private static readonly Dictionary<string, Converter<X, object>> cached;
    
      public Converter<X, object> this[string propertyName]
      {
        get {
          Converter<X, object> result;
          lock (this) if (!cached.TryGetValue(propertyName, out result)) {
            PropertyInfo pi = typeof(X).GetProperty(propertyName, true);
            if (pi == null) throw new ArgumentException("Type " + typeof(X).Name + " has no property named " + propertyName, propertyName);
             MethodInfo getter = pi.GetGetMethod();
             if (getter == null) throw new ArgumentException("Type " + typeof(X).Name + " has a property named " + propertyName + " but it is not readable", propertyName);
             result = (Converter<X, object>)Delegate.CreateDelegate(typeof (Converter<X, object>), getter);
             cached.Add(propertyName, result);
           }
           return result;
         }
      }
    }
    
    public class Pair<S,T>
    {
       public readonly S first;
       public readonly T second;
       public Pair(S s, T t) { first = s; second = t; }
    }
    
    List<Pair<X, Y>> FindCommonEntries<X, Y>(IEnumerable<X> listA, IEnumerable<Y> listB, string propertyNameA, string propertyNameB, out List<X> onlyA, out List<Y> onlyB)
    {
        return FindCommonEntries<X,Y>(listA, listB, PropertyGetter<X>[propertyName], PropertyGetter<Y>[propertyName], out onlyA, out onlyB);
    }
    
    List<Pair<X, Y>> FindCommonEntries<X, Y>(IEnumerable<X> listA, IEnumerable<Y> listB, Converter<X, object> getA, Converter<Y, object> getB, out List<X> onlyA, out List<Y> onlyB)
    {
        Dictionary<object, Pair<List<X>, bool>> mapA = new Dictionary<object, X>();
        foreach (X x in listA) {
          Pair<List<X>,bool> set;
          object key = getA(x);
          if (!mapA.TryGetValue(key, out set))
            mapA.Add(key, set = new Pair<List<X>, bool>(new List<X>(), false));
          set.first.Add(x);
        }
    
        onlyB = new List<Y>();
        List<Pair<X, Y>> common = new List<Pair<X, Y>>();
        foreach (Y y in listB) {
          Pair<List<X>,bool> match;
          if (mapA.TryGetValue(getB(y), out match)) {
            foreach (X x in match.first) common.Add(x, y);
            match.second = true;
          }
          else
            onlyB.Add(y);
        }
    
        onlyA = new List<X>();
        foreach (Pair<List<X>, bool> set in mapA.Values) {
          if (!set.second) onlyA.AddRange(set.first);
        }
    
        return common;
    }
    

    EDIT:分离的反射代码,因此可以通过传入lambda来避免。

    编辑:使用 Converter Func

    EDIT:缓存属性getter委托以避免为每个列表(仅为每个列表类型)支付反射开销。

    Pair<S,T> 类以消除对.NET的最终依赖性>2.0.

    注意:这个答案的复杂性很大程度上涉及到指定属性中的条目不唯一的可能性。我返回匹配项的笛卡尔积,但不计算两个完整列表的笛卡尔积。如果假设键属性的唯一性,事情可以简化很多。

        4
  •  2
  •   Mike M.    14 年前
    var elementInBoth = (from x in ListOfClassX
                         join y in ListOfClassY on x.T1 equals y.T1
                         select x).ToList();
    
    var elementsOnlyInListOfClassX = ListOfClassX.Except(elementsInBoth);
    

    var elementsToRemoveFromListOfClassY = (from x in ListOfClassX
                                            join y in ListOfClass Y on x.T1 equals y.T1
                                            select y).ToList();
    
    var elementsOnlyInListOfClassY = ListOfClassY.Except(elementsToRemoveFromListOfClassY);
    
        5
  •  2
  •   Abel    14 年前

    我的第二个答案,作为一个新的答案,因为它是相当不同于我以前的答案,都有自己的价值

    我假设,对于下面的代码,更改类不是一个选项。我也遵循OP的要求使用泛型。代码是初步的,但应该与小的增强工作。代码已经过测试并运行正常。

    要解决的问题不是泛型,而是反射,简单地说,对于这个场景来说,反射太慢了,这就是为什么我在第二个代码块中向您展示如何应用代理的手动缓存以及如何找到gettor的方法。

    // this is how you use the Comparisons class:
    List<A> aList = new List<A>();
    List<B> bList = new List<B>();
    aList.Add(new A("first3"));
    aList.Add(new A("duplicate4"));
    aList.Add(new A("duplicate1"));
    aList.Add(new A("first2"));
    bList.Add(new B("second3"));
    bList.Add(new B("duplicate4"));
    bList.Add(new B("duplicate1"));
    bList.Add(new B("second2"));
    
    
    // get all elements that are in both lists (duplicate1 and duplicate4)
    var listDuplicates = Comparisons.GetDuplicatesFromList1(aList, bList);
    
    // remove duplicates (keep "first3" and "first2")
    var withoutDuplicates = aList.Except(listDuplicates).ToList();
    

    下面是实际代码。我评论了更难的部分。它不是您将遇到的最简单的代码类型,要理解或应用它,您需要了解反射、委托和方法调用技术。

    // all logic goes into this class
    public static class Comparisons
    {
        // note: static, so don't use in multi-threading environments!
        // must use Delegate as type here, Func<XX, string> would not work, as we cannot possibly know what XX is
        // up front. This is not a problem, as Delegate is the parent of all Func<> and Action<>
        static Dictionary<Type, Delegate> methodLookup = new Dictionary<Type, Delegate>();
    
        private static Func<T, string> EnsureMethod<T>(T obj)
            where T : class, new()
        {
            Type type = obj.GetType();
    
            if(!methodLookup.ContainsKey(type))
            {
                // The tricky bit. We cannot use GetProperty here, because we later need a method
                // and we cannot use GetMethod, because it cannot find special methods (hidden gettors)
                MemberInfo[] members = type.GetMember("get_T1");
                if(members == null || members.Length > 1)
                    throw new InvalidOperationException("Object must have one 'T1' gettor property");
    
                MethodInfo property = members[0] as MethodInfo;
    
                if(property == null)
                    throw new InvalidOperationException("Object must have 'T1' property");
    
    
                // creating a delegate is the best way to speed up method invocation
                // this type of delegate is called an "open instance delegate", which is like
                // a static delegate with first parameter as the object to invoke on
                Func<T, string> propertyGettor = (Func<T, string>) Delegate.CreateDelegate(typeof(Func<T, string>), null, property);
                methodLookup.Add(type, propertyGettor);
            }
    
            // must cast here
            return (Func<T, string>)methodLookup[obj.GetType()];
    
        }
    
        // I use a generic extension method here. This is frowned upon by some language purists
        // you can always use a utility helper method, which is the alternative
        public static string GetPropertyT1<T>(this T obj)
            where T : class, new()
        {
            // do something with obj1 being null, this is the BCL default
            if (obj == null)
                throw new ArgumentNullException("Extension method object cannot be null for GetT1 method");
    
            // if the property is not found, an error is raised, so the following is safe:
            // only the first invocation for each type (class) of object is relatively slow
            Func<T, string> delegateObj1 = EnsureMethod(obj);
    
    
            // this now is lightning fast: it invokes the method on the instance of obj
            return delegateObj1.Invoke(obj);
        }
    
        // The actual method that does something, it will return all elements in list1
        // that are also found in list2, replace this with whatever logic you need
        public static IList<U> GetDuplicatesFromList1<U, V>(IEnumerable<U> list1, IEnumerable<V> list2)
            where U: class, new()
            where V: class, new()
        {
            var elementsList1InBoth = from x in list1
                                      join y in list2 on x.GetPropertyT1() equals y.GetPropertyT1()
                                      select x;
    
            return elementsList1InBoth.ToList();
        }
    
    
    }
    
    
    // your original classes as A and B, with no inheritance chain or other relations
    public class A
    {
        public A(){}
        public A(string value) { this.T1 = value; }
        public string T1 { get; set; }
    }
    public class B
    {
        public B(){}
        public B(string value) { this.T1 = value; }
        public string T1 { get; set; }
        public string Tx { get; set; }
    }