为何Linq的Distinct实在是不给力
程序员文章站
2023-12-19 20:59:46
假设我们有一个类:productpublic class product{ public string id { get; set; }...
假设我们有一个类:product
public class product
{
public string id { get; set; }
public string name { get; set; }
}
main函数如下:
static void main()
{
list<product> products = new list<product>()
{
new product(){ id="1", name="n1"},
new product(){ id="1", name="n2"},
new product(){ id="2", name="n1"},
new product(){ id="2", name="n2"},
};
var distinctproduct = products.distinct();
console.readline();
}
可以看到distinctproduct 的结果是:
因为distinct 默认比较的是product对象的引用,所以返回4条数据。
那么如果我们希望返回id唯一的product,那么该如何做呢?
distinct方法还有另一个重载:
//通过使用指定的 system.collections.generic.iequalitycomparer<t> 对值进行比较
//返回序列中的非重复元素。
public static ienumerable<tsource> distinct<tsource>(this ienumerable<tsource> source,
iequalitycomparer<tsource> comparer);
该重载接收一个iequalitycomparer的参数。
假设要按id来筛选,那么应该新建类productidcomparer 内容如下:
public class productidcomparer : iequalitycomparer<product>
{
public bool equals(product x, product y)
{
if (x == null)
return y == null;
return x.id == y.id;
}
public int gethashcode(product obj)
{
if (obj == null)
return 0;
return obj.id.gethashcode();
}
}
使用的时候,只需要
var distinctproduct = products.distinct(new productidcomparer());
结果如下:
现在假设我们要 按照 name来筛选重复呢?
很明显,需要再添加一个类productnamecomparer.
那能不能使用泛型类呢??
新建类propertycomparer<t> 继承iequalitycomparer<t> 内容如下:
public class propertycomparer<t> : iequalitycomparer<t>
{
private propertyinfo _propertyinfo;
/// <summary>
/// 通过propertyname 获取propertyinfo对象
/// </summary>
/// <param name="propertyname"></param>
public propertycomparer(string propertyname)
{
_propertyinfo = typeof(t).getproperty(propertyname,
bindingflags.getproperty | bindingflags.instance | bindingflags.public);
if (_propertyinfo == null)
{
throw new argumentexception(string.format("{0} is not a property of type {1}.",
propertyname, typeof(t)));
}
}
#region iequalitycomparer<t> members
public bool equals(t x, t y)
{
object xvalue = _propertyinfo.getvalue(x, null);
object yvalue = _propertyinfo.getvalue(y, null);
if (xvalue == null)
return yvalue == null;
return xvalue.equals(yvalue);
}
public int gethashcode(t obj)
{
object propertyvalue = _propertyinfo.getvalue(obj, null);
if (propertyvalue == null)
return 0;
else
return propertyvalue.gethashcode();
}
#endregion
}
主要是重写的equals 和gethashcode 使用了属性的值比较。
使用的时候,只需要:
//var distinctproduct = products.distinct(new propertycomparer<product>("id"));
var distinctproduct = products.distinct(new propertycomparer<product>("name"));
结果如下:
为什么微软不提供propertyequality<t> 这个类呢?
按照上面的逻辑,这个类应该没有很复杂啊,细心的同学可以发现propertyequality 大量的使用了反射。每次获取属性的值的时候,都在调用
_propertyinfo.getvalue(x, null);
可想而知,如果要筛选的记录非常多的话,那么性能无疑会受到影响。
为了提升性能,可以使用表达式树将反射调用改为委托调用,
具体代码如下:
public class fastpropertycomparer<t> : iequalitycomparer<t>
{
private func<t, object> getpropertyvaluefunc = null;
/// <summary>
/// 通过propertyname 获取propertyinfo对象
/// </summary>
/// <param name="propertyname"></param>
public fastpropertycomparer(string propertyname)
{
propertyinfo _propertyinfo = typeof(t).getproperty(propertyname,
bindingflags.getproperty | bindingflags.instance | bindingflags.public);
if (_propertyinfo == null)
{
throw new argumentexception(string.format("{0} is not a property of type {1}.",
propertyname, typeof(t)));
}
parameterexpression exppara = expression.parameter(typeof(t), "obj");
memberexpression me = expression.property(exppara, _propertyinfo);
getpropertyvaluefunc = expression.lambda<func<t, object>>(me, exppara).compile();
}
#region iequalitycomparer<t> members
public bool equals(t x, t y)
{
object xvalue = getpropertyvaluefunc(x);
object yvalue = getpropertyvaluefunc(y);
if (xvalue == null)
return yvalue == null;
return xvalue.equals(yvalue);
}
public int gethashcode(t obj)
{
object propertyvalue = getpropertyvaluefunc(obj);
if (propertyvalue == null)
return 0;
else
return propertyvalue.gethashcode();
}
#endregion
}
可以看到现在获取值只需要getpropertyvaluefunc(obj) 就可以了。
使用的时候:
var distinctproduct = products.distinct(new fastpropertycomparer<product>("id")).tolist();
public class product
{
public string id { get; set; }
public string name { get; set; }
}
main函数如下:
static void main()
{
list<product> products = new list<product>()
{
new product(){ id="1", name="n1"},
new product(){ id="1", name="n2"},
new product(){ id="2", name="n1"},
new product(){ id="2", name="n2"},
};
var distinctproduct = products.distinct();
console.readline();
}
可以看到distinctproduct 的结果是:
因为distinct 默认比较的是product对象的引用,所以返回4条数据。
那么如果我们希望返回id唯一的product,那么该如何做呢?
distinct方法还有另一个重载:
//通过使用指定的 system.collections.generic.iequalitycomparer<t> 对值进行比较
//返回序列中的非重复元素。
public static ienumerable<tsource> distinct<tsource>(this ienumerable<tsource> source,
iequalitycomparer<tsource> comparer);
该重载接收一个iequalitycomparer的参数。
假设要按id来筛选,那么应该新建类productidcomparer 内容如下:
public class productidcomparer : iequalitycomparer<product>
{
public bool equals(product x, product y)
{
if (x == null)
return y == null;
return x.id == y.id;
}
public int gethashcode(product obj)
{
if (obj == null)
return 0;
return obj.id.gethashcode();
}
}
使用的时候,只需要
var distinctproduct = products.distinct(new productidcomparer());
结果如下:
现在假设我们要 按照 name来筛选重复呢?
很明显,需要再添加一个类productnamecomparer.
那能不能使用泛型类呢??
新建类propertycomparer<t> 继承iequalitycomparer<t> 内容如下:
public class propertycomparer<t> : iequalitycomparer<t>
{
private propertyinfo _propertyinfo;
/// <summary>
/// 通过propertyname 获取propertyinfo对象
/// </summary>
/// <param name="propertyname"></param>
public propertycomparer(string propertyname)
{
_propertyinfo = typeof(t).getproperty(propertyname,
bindingflags.getproperty | bindingflags.instance | bindingflags.public);
if (_propertyinfo == null)
{
throw new argumentexception(string.format("{0} is not a property of type {1}.",
propertyname, typeof(t)));
}
}
#region iequalitycomparer<t> members
public bool equals(t x, t y)
{
object xvalue = _propertyinfo.getvalue(x, null);
object yvalue = _propertyinfo.getvalue(y, null);
if (xvalue == null)
return yvalue == null;
return xvalue.equals(yvalue);
}
public int gethashcode(t obj)
{
object propertyvalue = _propertyinfo.getvalue(obj, null);
if (propertyvalue == null)
return 0;
else
return propertyvalue.gethashcode();
}
#endregion
}
主要是重写的equals 和gethashcode 使用了属性的值比较。
使用的时候,只需要:
//var distinctproduct = products.distinct(new propertycomparer<product>("id"));
var distinctproduct = products.distinct(new propertycomparer<product>("name"));
结果如下:
为什么微软不提供propertyequality<t> 这个类呢?
按照上面的逻辑,这个类应该没有很复杂啊,细心的同学可以发现propertyequality 大量的使用了反射。每次获取属性的值的时候,都在调用
_propertyinfo.getvalue(x, null);
可想而知,如果要筛选的记录非常多的话,那么性能无疑会受到影响。
为了提升性能,可以使用表达式树将反射调用改为委托调用,
具体代码如下:
public class fastpropertycomparer<t> : iequalitycomparer<t>
{
private func<t, object> getpropertyvaluefunc = null;
/// <summary>
/// 通过propertyname 获取propertyinfo对象
/// </summary>
/// <param name="propertyname"></param>
public fastpropertycomparer(string propertyname)
{
propertyinfo _propertyinfo = typeof(t).getproperty(propertyname,
bindingflags.getproperty | bindingflags.instance | bindingflags.public);
if (_propertyinfo == null)
{
throw new argumentexception(string.format("{0} is not a property of type {1}.",
propertyname, typeof(t)));
}
parameterexpression exppara = expression.parameter(typeof(t), "obj");
memberexpression me = expression.property(exppara, _propertyinfo);
getpropertyvaluefunc = expression.lambda<func<t, object>>(me, exppara).compile();
}
#region iequalitycomparer<t> members
public bool equals(t x, t y)
{
object xvalue = getpropertyvaluefunc(x);
object yvalue = getpropertyvaluefunc(y);
if (xvalue == null)
return yvalue == null;
return xvalue.equals(yvalue);
}
public int gethashcode(t obj)
{
object propertyvalue = getpropertyvaluefunc(obj);
if (propertyvalue == null)
return 0;
else
return propertyvalue.gethashcode();
}
#endregion
}
可以看到现在获取值只需要getpropertyvaluefunc(obj) 就可以了。
使用的时候:
var distinctproduct = products.distinct(new fastpropertycomparer<product>("id")).tolist();