当类型变化时,我无法在带有坐标的绑定列表中获取和设置项的值。
例如,假设我有三个类:
public class Client{
public string Name{ get; set; }
}
public class Debt{
public string AccountType{ get; set; }
public int DebtValue { get; set; }
}
public class Accounts{
public string Owner{ get; set; }
public int AccountNumber { get; set; }
public bool IsChekingAccount { get; set; }
}
然后,三个绑定列表(想象它们被填充):
public BindingList<Client> listOne;
public BindingList<Debt> listTwo;
public BindingList<Accounts> listThree;
我正在尝试创建一个扩展方法,该方法返回一个具有请求值的Object,或者设置值(如果已提供)。
public static Object GetValueByCoordinates(this IBindingList list, int x, int y) { /*some magic*/ }
public static Object SetValueByCoordinates(this IBindingList list, int x, int y, Object value) { /*some other magic*/ }
因此,例如,我需要能够在listThree中设置项目(2,3)的值,并在listTwo中设置值(1,1):
listThree.SetValueByCoordinates(2,3,false);
listThree.SetValueByCoordinates(1,1,"My self");
或从listOne和listTwo获取值(1,1)和(2,2):
string result = listOne.GetValueByCoordinates(1,1).ToString();
intresult = Convert.ToInt32(listOne.GetValueByCoordinates(1,1));
你将如何实现这种行为?我正在考虑使用反射,但我对它几乎一无所知。
请注意,方法必须以这种方式调用,因此必须避免使用这样的方法
public static Object GetValueByCoordinates<T>(this BindingList<T> list, int x, int y) { /*some magic*/ }
任何帮助将不胜感激。
答案 0 :(得分:4)
如上所述,我非常怀疑您寻求帮助的方法可能是解决您尝试解决的更广泛问题的最佳或最合适的方法。它可以完成(并且没有太大困难),但是生成的代码很难维护,容易出错,并且不易读取(导致前两个问题)。
也就是说,有很多不同的方法可以实现您所要求的特定行为。即使这不是解决当前问题的最佳方法,基本技术对于了解其他类型的问题也很有用。考虑到这一点,以下是您可能解决问题的两种最明显的方法。
手动配置从索引到getter和setter的映射:
恕我直言,这是最好的方式。不是因为它优雅或易于扩展,而是因为它 其中任何一个都不是。要求代码维护者明确地创建数据结构元素以支持您要处理的每种类型和属性,这将阻止此技术扩散到其他相关问题,甚至是当前问题。它甚至可以鼓励某人花更多的时间思考更广泛的问题,以便找到更好的策略。
这种方法确实具有合理的性能优势。因为代码是在编译时生成的,所以唯一真正的开销是值类型的装箱。有一些转换,但对于引用类型,开销实际上是不可测量的,甚至拳击开销可能不会显示在配置文件上,具体取决于可能使用此代码的密集程度。
这个特殊的解决方案如下所示:
static class ManualIndexedProperty
{
public static void SetValueByCoordinates(this IBindingList list, int x, int y, object value)
{
object o = list[x];
_typeToSetter[o.GetType()][y](o, value);
}
public static object GetValueByCoordinates(this IBindingList list, int x, int y)
{
object o = list[x];
return _typeToGetter[o.GetType()][y](o);
}
private static readonly Dictionary<Type, Func<object, object>[]> _typeToGetter =
new Dictionary<Type, Func<object, object>[]>()
{
{
typeof(Client),
new Func<object, object>[]
{
o => ((Client)o).Name
}
},
{
typeof(Debt),
new Func<object, object>[]
{
o => ((Debt)o).AccountType,
o => ((Debt)o).DebtValue,
}
},
{
typeof(Accounts),
new Func<object, object>[]
{
o => ((Accounts)o).Owner,
o => ((Accounts)o).AccountNumber,
o => ((Accounts)o).IsChekingAccount,
}
},
};
private static readonly Dictionary<Type, Action<object, object>[]> _typeToSetter =
new Dictionary<Type, Action<object, object>[]>()
{
{
typeof(Client),
new Action<object, object>[]
{
(o1, o2) => ((Client)o1).Name = (string)o2
}
},
{
typeof(Debt),
new Action<object, object>[]
{
(o1, o2) => ((Debt)o1).AccountType = (string)o2,
(o1, o2) => ((Debt)o1).DebtValue = (int)o2,
}
},
{
typeof(Accounts),
new Action<object, object>[]
{
(o1, o2) => ((Accounts)o1).Owner = (string)o2,
(o1, o2) => ((Accounts)o1).AccountNumber = (int)o2,
(o1, o2) => ((Accounts)o1).IsChekingAccount = (bool)o2,
}
},
};
}
声明两个词典,每个词典用于设置和获取属性值。字典将元素对象的类型映射到委托实例数组以执行实际工作。每个委托实例引用一个匿名方法,该方法已经过手动编码以执行必要的操作。
这种方法的一个主要优点是明确且明显的是哪个索引对应于每种类型的属性。
如果您正在处理任何大量类型和/或属性,则设置此方法将非常繁琐且耗时。但恕我直言,这是一件好事。正如我上面提到的,希望这种方法的痛苦可以帮助说服某人放弃通过索引完全访问属性的想法。 :)
如果这种单调乏味是不可接受的,但你仍然坚持使用索引属性访问方法,那么你实际上可以使用反射作为替代...
使用反射来访问属性:
这种技术更具动感。一旦实现,它适用于任何类型的对象而无需修改,并且不需要额外的工作来支持新类型。
一个主要的缺点是为了产生一致的,可预测的结果,它按名称对属性进行排序。这可以确保C#编译器和/或CLR中的更改不会破坏代码,但这意味着您无法在不更新通过索引访问这些属性的代码的情况下添加或删除类型中的属性。 / p>
在我的演示使用代码中(请参阅下文),我通过声明为enum
属性名称提供int
值的static class ReflectionIndexedProperty
{
public static void SetValueByCoordinates(this IBindingList list, int x, int y, object value)
{
object o = list[x];
GetProperty(o, y).SetValue(o, value);
}
public static object GetValueByCoordinates(this IBindingList list, int x, int y)
{
object o = list[x];
return GetProperty(o, y).GetValue(o);
}
private static PropertyInfo GetProperty(object o, int index)
{
Type type = o.GetType();
PropertyInfo[] properties;
if (!_typeToProperty.TryGetValue(type, out properties))
{
properties = type.GetProperties();
Array.Sort(properties, (p1, p2) => string.Compare(p1.Name, p2.Name, StringComparison.OrdinalIgnoreCase));
_typeToProperty[type] = properties;
}
return properties[index];
}
private static readonly Dictionary<Type, PropertyInfo[]> _typeToProperty = new Dictionary<Type, PropertyInfo[]>();
}
类型来解决此维护问题。如果代码实际引用具有文字索引值的属性,这将是一种帮助减少维护开销的好方法。
但是,您的方案可能涉及通过索引动态访问属性值,例如在序列化方案或类似的情况下。在这种情况下,如果属性被添加或删除到类型中,您还需要添加可以重新映射或处理索引值更改的内容。
坦率地说,无论哪种方式,类型索引更改的问题都是我强烈建议首先对这种索引访问属性的一个重要原因。但是,再次,如果你坚持......
PropertyInfo
在此版本中,代码检索给定类型的PropertyInfo
对象数组,按名称对该数组进行排序,为给定索引检索相应的PropertyInfo
对象,然后使用该PropertyInfo
对象。 1}}对象设置或获取属性值,视情况而定。
反射导致显着的运行时性能开销。此特定实现通过缓存enum
对象的已排序数组来减轻某些的开销。这样,它们只需要创建一次,第一次代码必须处理给定类型的对象。
演示代码:
正如我所提到的,为了更容易比较两种方法而不必去每个方法调用并手动更改用于调用的整数文字,我创建了一些简单的class Program
{
static void Main(string[] args)
{
BindingList<Client> listOne = new BindingList<Client>()
{
new Client { Name = "ClientName1" },
new Client { Name = "ClientName2" },
new Client { Name = "ClientName3" },
};
BindingList<Debt> listTwo = new BindingList<Debt>()
{
new Debt { AccountType = "AccountType1", DebtValue = 29 },
new Debt { AccountType = "AccountType2", DebtValue = 31 },
new Debt { AccountType = "AccountType3", DebtValue = 37 },
};
BindingList<Accounts> listThree = new BindingList<Accounts>()
{
new Accounts { Owner = "Owner1", AccountNumber = 17, IsChekingAccount = false },
new Accounts { Owner = "Owner2", AccountNumber = 19, IsChekingAccount = true },
new Accounts { Owner = "Owner3", AccountNumber = 23, IsChekingAccount = true },
};
LogList(listThree);
listThree.SetValueByCoordinates(2, (int)AccountsProperty.IsChekingAccount, false);
listThree.SetValueByCoordinates(1, (int)AccountsProperty.Owner, "My self");
LogList(listThree);
string result1 = (string)listOne.GetValueByCoordinates(0, (int)ClientProperty.Name);
int result2 = (int)listTwo.GetValueByCoordinates(1, (int)DebtProperty.DebtValue);
LogList(listOne);
LogList(listTwo);
Console.WriteLine("result1: " + result1);
Console.WriteLine("result2: " + result2);
}
static void LogList<T>(BindingList<T> list)
{
foreach (T t in list)
{
Console.WriteLine(t);
}
Console.WriteLine();
}
}
类型来表示属性索引。我还写了一些代码来初始化一些可以测试的列表。
注意:需要指出的一件非常重要的事情是,在您的问题中,您对如何索引属性并不十分一致。在我的代码示例中,我选择坚持使用基于0的索引(与C#数组和其他集合中使用的自然索引一致)。您当然可以使用不同的基础(例如,基于1的索引),但是您需要确保在整个代码中完全一致(包括在实际索引数组时从传入索引中减去1)。
我的演示代码如下所示:
object
请注意,我使用简单转换从ToString()
转换为特定类型,包括设置属性值和获取它们。这是一种比例如更好的方法。致电Convert.ToInt32()
或ToString()
;你确切知道该类型应该是什么,它既可以是该类型的实际实例(对于引用类型),也可以是盒装实例(对于值类型),并且无论是哪种方式都可以完全满足您的需要。
我还在示例类中添加了public class Client
{
public string Name { get; set; }
public override string ToString()
{
return "{" + Name + "}";
}
}
public class Debt
{
public string AccountType { get; set; }
public int DebtValue { get; set; }
public override string ToString()
{
return "{" + AccountType + ", " + DebtValue + "}";
}
}
public class Accounts
{
public string Owner { get; set; }
public int AccountNumber { get; set; }
public bool IsChekingAccount { get; set; }
public override string ToString()
{
return "{" + Owner + ", " + AccountNumber + ", " + IsChekingAccount + "}";
}
}
覆盖,以便更容易查看输出:
enum
最后,这是使用的enum ClientProperty
{
Name = 0
}
enum DebtProperty
{
AccountType = 0,
DebtValue = 1
}
enum AccountsProperty
{
Owner = 0,
AccountNumber = 1,
IsChekingAccount = 2,
}
声明:
手动编制索引:
enum ClientProperty
{
Name = 0
}
enum DebtProperty
{
AccountType = 0,
DebtValue = 1
}
enum AccountsProperty
{
AccountNumber = 0,
IsChekingAccount = 1,
Owner = 2,
}
反映/按名称排序:
Expression
当然,这些都可能是相同的值。也就是说,虽然您无法控制排序顺序,但一旦给出了属性名称,手动版本就可以按名称排序顺序声明手动编写的lambda,以便相同的索引可以正常工作无论哪种方式。你决定做什么并不重要;它只需要保持一致。
最后的想法......
我是否已经提到过我强烈建议不要围绕此技术构建任何大量代码?你根本不清楚你正在尝试解决的实际大局问题是什么,但是出现这种问题的方法却有很多种,这很可能会带来很多问题。难以找到,耗时修复代码中的错误。
就性能而言,只要您没有在大量对象和属性值的紧密循环中执行代码,上述内容就不会太糟糕。手动(第一)示例尤其应该相对较快。通过使用{{1}}类型,可以通过手动方法的最小开销实现基于反射的方法的通用设计。这有点复杂,但是有一个优势,你可以动态生成表达式,最终有效地成为手动方法的编译代码实现。