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






4.33/5 (3投票s)
使用自定义属性动态生成 LINQ 查询。
引言
上周我玩了 LINQ,它很有趣,直到我尝试动态生成一个查询。这可以做到,但不是我希望的那样。
背景
我创建了一个 Car
类,它有两个属性,Name
和 Year
,并且希望能够根据网格中显示的 List
(按 Name
或按 Year
)中选定的列来筛选我的 Cars List
。
当我想这样做时,我不喜欢我在 Google 上找到的选项(反射、表达式、谓词、Dynamic LINQ 库……),并认为它可能更容易……
对我来说,主要问题似乎是进行筛选的属性必须在我编写代码时设置,因此在我更改代码之前,我无法更改属性,或者编写几个查询并在它们上面做一些事情……不好。
然后我发现(据我所知)获取属性名称的唯一方法是通过反射……再次不好。所以我想为什么不创建自定义属性,即像 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>
不是字符串。
所以我为我的自定义 Property 结构编写了一个新的隐式运算符重载。
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 毫秒就找到了很多对象中的一个。 我的电脑有 4 GB 的 DDR2 和 3.2 Ghz 的 CoreDuo CPU。
问题
我认为有些事情可以做得更好,例如,如果我不必将属性 n 次添加到我的 Properties
集合中,并且如果我可以使用 T
而不是 dynamic
并且每次都必须设置 PropertyName
确实让我很烦。 但就目前而言,就是这样,如果我找到一种更好更简单的方法来做到这一点,我会通知你 :)