如何使用泛型比较C#中的X列表和Y列表?
我有两个类,X和Y。两个类都有相同的相似属性,如下所示如何使用泛型比较C#中的X列表和Y列表?,c#,.net,generics,list,C#,.net,Generics,List,我有两个类,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 { g
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上
我该怎么做呢?如果你想要一个可重复使用的答案,而不是特定于
类X
和类Y
,你需要反思。看一看
编辑:我似乎得到了不熟悉反射的人的反对票,所以我将添加一些示例源代码:
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;
}
静态类属性编写器
{
私有静态只读字典缓存;
公共转换器此[string propertyName]
{
得到{
转换器结果;
如果(!cached.TryGetValue(propertyName,out结果))锁定(此){
PropertyInfo pi=typeof(X).GetProperty(propertyName,true);
如果(pi==null)抛出新的ArgumentException(“Type”+typeof(X).Name+”没有名为“+propertyName,propertyName”的属性);
MethodInfo getter=pi.getMethod();
如果(getter==null)抛出新的ArgumentException(“Type”+typeof(X).Name+”有一个名为“+propertyName+”但不可读的属性,propertyName”);
result=(Converter)Delegate.CreateDelegate(typeof(Converter),getter);
Add(propertyName,result);
}
返回结果;
}
}
}
公共类对
{
公众只读优先;
公众只读T秒;
公共对(S,T){first=S;second=T;}
}
列出FindCommonEntries(IEnumerable列表A、IEnumerable列表B、string propertyNameA、string propertyNameB、仅输出列表A、仅输出列表B)
{
返回FindCommonEntries(listA、listB、PropertyGetter[propertyName]、PropertyGetter[propertyName]、out onlyA、out onlyB);
}
列出FindCommonEntries(IEnumerable listA、IEnumerable listB、Converter getA、Converter getB、out List onlyA、out List onlyB)
{
Dictionary mapA=新字典();
foreach(列表中的X){
对集;
对象键=getA(x);
如果(!mapA.TryGetValue(键,偏移))
Add(key,set=newpair(newlist(),false));
设置。首先,添加(x);
}
onlyB=新列表();
列表公共=新列表();
foreach(列表B中的Y){
配对;
if(mapA.TryGetValue(getB(y),不匹配)){
foreach(X在match.first中)common.Add(X,y);
match.second=true;
}
其他的
仅b.添加(y);
}
onlyA=新列表();
foreach(在mapA.Values中设置对){
如果(!set.second)仅为a.AddRange(set.first);
}
返回普通;
}
编辑:添加了不匹配的元素列表
编辑:分离的反射代码,因此可以通过传入lambda来避免
编辑:使用委托类型而不是Func
,因为它在.NET 2.0中可用,并且实际上更好地描述了用法
编辑:缓存属性getter委托以避免为每个列表(仅为每个列表类型)支付反射开销
编辑:添加了Pair
类以摆脱对.NET>2.0的最终依赖
注意:这个答案的复杂性很大程度上涉及指定属性中的条目不唯一的可能性。我返回匹配项的笛卡尔乘积,但不计算整个列表的笛卡尔乘积。如果假定key属性的唯一性,事情可以简化很多。最好的方法是首先创建一个只包含
T1
的接口。然后从该接口继承每个类,如X
和Y
。现在,您可以基于此接口轻松创建泛型类或任何帮助器类
或者,您可以使用反射,或者如果您使用C#4.0,您可以使用dynamic
。对于(大型)列表,经典反射是一种降低速度的方法,所以除非缓存方法调用,否则不应该采用这种方法。然而,C#4.0通过DLR提供了方法缓存,这在大多数情况下都足够快
或者(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; }
}
//接口,从IComparable继承
公共接口IX:IComparable
{
字符串T1{get;set;}
}
//创建一个基类
类别XBase:IX
{
公共字符串T1{get;set;}
公共国际比较(IX obj)
{
返回这个.T1.equals(obj.T1);
}
}
//从基类继承所有其他类
X类:XBase
{
公共字符串T2{get;set;}
公共字符串T3{get;set;}
}
Y类:XBase
{
公共字符串T2{get;set;}
公共字符串T3{get;set;}
公共strign O1{get;set;}
}
还有很多其他的方法。上面最后一种方法的优点是只需编写一次
T1
和CompareTo
的逻辑,这样可以避免混乱,并使代码清晰明了。我很难理解这个问题。但我把它理解为“如何根据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));
IEnumerable intersectionT1s=listX.Select(x=>x.T1).Intersect(listY.Select(y=>y.T1);
IEnumerable intersection=listX.Where(x=>intersectionT1s.Contains(x.T1));
IEnumerable 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 IntersectionOn(此IEnumerable左,
IEnumerable right,Func leftSelector,
IEnumerable<X> intersection = listX.IntersectionOn(listY, x => x.T1, y => y.T1);
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);
// 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; }
}