65.9K
CodeProject 正在变化。 阅读更多。
Home

使用自定义属性动态生成 LINQ 查询

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (3投票s)

2012年3月23日

CPOL

4分钟阅读

viewsIcon

40140

使用自定义属性动态生成 LINQ 查询。

引言

上周我玩了一下 LINQ,这很有趣,直到我尝试动态生成一个查询。 这是可以做到的,但不是我希望的那样。

背景

我创建了一个 Car 类,它有两个属性,NameYear,并且希望能够根据我在网格中选择的列来过滤我的 Cars List,该网格显示一个 List(按 Name 或按 Year)。

当我尝试这样做时,我不喜欢我在 Google 上找到的选项(反射、表达式、谓词、Dynamic LINQ 库……),并认为这可能会更容易……

对我来说的主要问题似乎是,我进行过滤的属性必须在我编写代码时设置,因此在我更改代码之前,我无法更改属性,或者编写几个查询并对它们进行一些处理……不好。

然后我发现获得我的属性名称的唯一方法(据我所知)是通过 Reflection... 再次不好。 所以我想为什么不创建自定义属性,这些属性知道它们的名称,如 car.Name.PropertyName

所以我写了这个小结构

public struct Property<T> 
{ 
  #region fields
    private string _name;
    private T _value;
   #endregion             
   #region Properties
         public string PropertyName
         {get { return _name; }set { _name = value; }}
         public dynamic PropertyValue
         {get { return _value; }set { _value = value; }
         }         
      #endregion
}

我的 Car 类将是这样的

public class Car
{
    
    private Property<string> _name;

    public Property<string> Name
    {
        get {_name.PropertyName = "Name";
            return _name;
        }
        set { 
            _name = value;
        }
    }

    private Property<int> _year;

        public Property<int> Year
    {
        get {_year.PropertyName = "Year";
            return _year;
        }
              set { _year = value;
          
        }
    }
}

这还可以,但问题出现在我的 buton_click

Car c1 = new Car(); c1.Name = "Audi";

因为 c1.Name 不是字符串,所以我将一个隐式运算符重载添加到我的自定义属性结构中,如下所示

public static implicit operator Property<T>(T temp )
{
    var local = new Property<T> {PropertyValue = temp};            
    return local;
}

但是当我这样做时,我失去了我的原始 Car 实例,因为在这个方法中,正如您所看到的,我创建了一个新的 Property<T>,并且这个新的 Property<T> 没有设置 PropertyName

这就是为什么我在我的 Car 类中,在每个属性上,propertyname = "TheNameOfProperty",然后返回值。

错误消失了,但我也必须能够编写类似这样的内容

string carName = c1.Name

此外,c1.Name return Property<T> 不是一个字符串。

所以我在我的自定义属性结构中编写了一个新的隐式运算符重载。

public static implicit operator T(Property<T> tt)
{
    return tt.PropertyValue;
}

现在一切都很棒。

但是要使用自定义属性,我必须能够遍历我的 Car 实例中的属性集合(NameYear 等)

我将在我的 Car 类中添加一个 List,它将包含该实例的所有属性。 我在某个地方读到 HashSet 集合很快,所以我将把它用于此目的。

将属性添加到我的列表中的最佳位置是在属性的 setter 中,在 (_name=value) 之后。 但这里有一个技巧。 当我设置我的属性的值时

car1.Name = "Audi"

隐式运算符 Property<T> 发生并返回一个新的 Property 实例,所以在属性列表中,我们需要重写 Add 方法,以便它删除旧实例,在这种情况下是 Name 属性,并将其替换为新的实例。

/// <summary>
/// Custom made collection that need to be used in order custom property work correctly
/// it deletes old reference of property and add a new one
/// </summary>
public class ListOfProperties : System.Collections.Generic.HashSet<IProperty>
{
    public new bool Add(IProperty p)
    {
       int nesto = this.RemoveWhere(x => x.PropertyName.Equals(p.PropertyName));

        if (base.Add(p))
            return true;
        else
            return false;
    }
}

正如您所看到的,我们创建了两个接口以使事情更容易,IPropertyIProperties

public interface IProperty
{
    string PropertyName { get; set; }
    dynamic PropertyValue { get; set; }
}

public interface IProperties
{
    ListOfProperties Properties { get; }
}

所以我的自定义属性结构现在看起来像

public struct  Property<T> : IProperty
{

    #region fields

    private string _name;
    private T _value;

    #endregion

    #region Properties

    public string PropertyName
    {
        get { return _name; }
        set { _name = value; }
    }
    public dynamic PropertyValue
    {
        get { return _value; }
        set { _value = value; }
    }

    #endregion 

    #region Operator Overloading


    public static implicit operator Property<T>(T temp )
    {
        var local = new Property<T> {PropertyValue = temp};
        return local;
    }
 

    public static implicit operator T(Property<T> tt)
    {
        return tt.PropertyValue;
    }

    #endregion
}

使用代码

我的 Car 类现在看起来像这样

public class Car:IProperties
{
    #region Implementation of IProperties

    ListOfProperties list = new ListOfProperties();

    public ListOfProperties Properties
    {
        get { return list; }
    }

    #endregion

    private Property<string> _name;

    public Property<string> Name
    {
        get { _name.PropertyName = "Name";
            return _name;
        }
        set { 

            _name = value;
            Properties.Add(Name);
        }


    }

    private Property<int> _year;

    public Property<int> Year
    {
        get { _year.PropertyName = "Year";
            return _year;
        }
        set { _year = value;
            Properties.Add(Year);
        }
    }
}

对于那些拥有 Resharper 的人,这是一个模板(其他人可以跳过此步骤)

private Property<$type$> $field$;public Property<$type$> $property$
{
get { 
     $field$.PropertyName = "$property$";
     return $field$;
     }
set {
     $field$ = value;
     Properties.Add($property$);
     }
}

最后,在 button_click 上使用我们的代码(请注意“Name”可以是变量,并且用户必须知道 PropertyValue 的类型(例如,您必须传递字符串值对于“Name”,在这种情况下为“Audi”;如果您传递例如一个整数,将发生错误,因为 propertyValue 的类型是 dynamic)。

var cars = new List<Car>();
var car1 = new Car(){Name="Audi",Year=2010};
var car2 = new Car() {Name = "Mercedes", Year = 2012}; 
cars.Add(car1);
cars.Add(car2);
var audi = (from c in cars
           let carProperties = c.Properties
           from carProperty in carProperties
           where carProperty.PropertyName == "Name" 
           && carProperty.PropertyValue == "Audi"
           select c).FirstOrDefault();

我们也可以这样做

//Collection of all kinds of objects that implements IProperties
List<IProperties> AllKindsOfObjects = new List<IProperties>();

var car1 = new Car(){Name="Audi",Year=2010};

var car2 = new Car() {Name = "Mercedes", Year = 2012};

AllKindsOfObjects.Add(car1);
AllKindsOfObjects.Add(car2);


//some random object which too implements IProperties
var racun1 = new Racuni() {Ime = "Elvis", Prezime = "Begluk"};

AllKindsOfObjects.Add(racun1);

string temp = "Name";

var ListOfObjectsThatHavePropertyCalledName = from c in AllKindsOfObjects
           let objectProperties = c.Properties
           from objectProperty in objectProperties
           where objectProperty.PropertyName == temp 
           select c;

在上面的示例中,ListOfObjectsThatHavePropertyCalledName 将有两个对象,car1car2

测试速度

为了测试我的使用自定义属性动态生成的 LINQ 查询的查询速度,我使用了一个具有五个自定义属性的类,并在其中生成了 1,000,000 条记录的 List。对象的初始化并将对象添加到列表中花费了 6 秒。而查询本身仅花费 70 毫秒即可找到众多对象中的一个对象。 我的 PC 拥有 4 GB 的 DDR2 和 3.2 Ghz 的 CoreDuo CPU。

问题

我认为有些事情可以做得更好,例如,如果我不需要多次将属性添加到我的 Properties 集合中会更好,如果我可以使用 T 而不是 dynamic,并且我必须每次都设置 PropertyName 确实让我很烦。 但目前就这样了,如果我找到了一个更好更简单的方法来做到这一点,我会让你知道 :)

© . All rights reserved.