欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

为何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 的结果是:

为何Linq的Distinct实在是不给力

因为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());
结果如下:

为何Linq的Distinct实在是不给力

现在假设我们要 按照 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"));

结果如下:

为何Linq的Distinct实在是不给力

为什么微软不提供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();

上一篇:

下一篇: