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






4.33/5 (3投票s)
使用自定义属性动态生成 LINQ 查询。
引言
上周我玩了一下 LINQ,这很有趣,直到我尝试动态生成一个查询。 这是可以做到的,但不是我希望的那样。
背景
我创建了一个 Car
类,它有两个属性,Name
和 Year
,并且希望能够根据我在网格中选择的列来过滤我的 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
实例中的属性集合(Name
、Year
等)
我将在我的 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;
}
}
正如您所看到的,我们创建了两个接口以使事情更容易,IProperty
和 IProperties
。
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
将有两个对象,car1
和 car2
。
测试速度
为了测试我的使用自定义属性动态生成的 LINQ 查询的查询速度,我使用了一个具有五个自定义属性的类,并在其中生成了 1,000,000 条记录的 List
。对象的初始化并将对象添加到列表中花费了 6 秒。而查询本身仅花费 70 毫秒即可找到众多对象中的一个对象。 我的 PC 拥有 4 GB 的 DDR2 和 3.2 Ghz 的 CoreDuo CPU。
问题
我认为有些事情可以做得更好,例如,如果我不需要多次将属性添加到我的 Properties
集合中会更好,如果我可以使用 T
而不是 dynamic
,并且我必须每次都设置 PropertyName
确实让我很烦。 但目前就这样了,如果我找到了一个更好更简单的方法来做到这一点,我会让你知道 :)