我有一个实现多个属性的自定义类(调用i Field)。其中一个属性是MaximumLength,它指定值的最大长度。 Value属性是一个对象,所以我可以设置为string,int,double等。然后我有一个类,其中包含多个Field类型的属性。所有Field属性都在构造函数中初始化,并且只能写入Field.Value属性。我想要做的是,如果尝试将Field.Value设置为对于该字段而言太长的值并实现INotifyPropertyChanged,则抛出错误。我的问题是Value属性是泛型Field类的成员,我不知道如何在该类中获取属性的名称。
一个例子:
public class Customer
{
private Field _firstName = new Field(typeof(string), 20);
public Field FirstName
{
get
{
return _firstName;
}
}
}
public class Field
{
private Type _type;
private int _maximumLength;
object _value;
public Field(Type type, int maximumLength)
{
_type = type;
_maximumLength = maximumLength;
}
public Object Value
{
get
{
return _value;
}
set
{
if (value.ToString().Length > _maximumLength)
{
throw(string.Format("{0} cannot exceed {1} in length.", property name, _maximumValue);
}
else
{
_value = value;
OnPropertyChanged(property name);
}
}
}
}
希望这很清楚。
答案 0 :(得分:0)
为什么不简单地将Field类扩展为也保存一个名称,并在构造函数中传递它?
private Field _firstName = new Field(typeof(string), "FirstName", 20);
Field类看起来像这样:
public class Field
{
private Type _type;
private int _maximumLength;
private string _name;
object _value;
public Field(Type type, string name, int maximumLength)
{
_type = type;
_maximumLength = maximumLength;
_name = name;
}
public Object Value
{
get
{
return _value;
}
set
{
if (value.ToString().Length > _maximumLength)
{
throw new SomeException(string.Format("{0} cannot exceed {1} in length.", _name, _maximumValue));
}
else
{
_value = value;
OnPropertyChanged(_name);
}
}
}
}
答案 1 :(得分:0)
同意Fredrik。
但是,如果您希望在Field数据中使用某些类型安全性,也可以考虑使用泛型,即将Field类重新定义为存储T _value而不是对象的Field。
我还假设您必须使用不同的算法来检查不同类型的长度(例如字符串与数字等)以及空值检查等。
HTH。
答案 2 :(得分:0)
我同意上述内容。该字段应具有自己的Name属性,该属性使用构造设置。
我不同意使用泛型实现这一点。类型安全是一个很好的目标,但您可能还需要能够存储不同类型的字段对象的集合/列表。
如果泛型类也没有实现通用接口,泛型也不会让你这样做。
您可以实现一个通用接口,但是您需要Value为object类型,而不是类型< T> ,因此您将失去泛型的性能优势。
答案 3 :(得分:0)
我相信这会做你想要发生的事。我在类中添加了泛型,而不是使用object,以添加类型安全性。我还添加了一个显式异常类型,因此它可以编译,但Exception类型可以是任何带字符串的东西。
public class Customer
{
private Field<string> _firstName = new Field<string>("FirstName", 20);
public Field<string> FirstName
{
get
{
return _firstName;
}
}
}
public class Field<T>
{
private int _maximumLength;
private T _value;
private string _propertyName;
public Field(string propertyName, int maximumLength)
{
_propertyName = propertyName;
_maximumLength = maximumLength;
}
public T Value
{
get
{
return _value;
}
set
{
if (value.ToString().Length > _maximumLength)
{
throw new ArgumentException(string.Format("{0} cannot exceed {1} in length.", _propertyName, _maximumLength));
}
else
{
_value = value;
OnPropertyChanged(_propertyName);
}
}
}
}
编辑:
回应你的评论:“我已经考虑了这一点,但对我来说这似乎是多余的,因为该属性有一个名称,如果我或其他人开始重命名属性,这是一个额外的步骤。我可能会回到这一点,但如果我无法得到另一种方法。“
我相信你所要求的是某种反射代码,当从Field通知属性更改时,它将检索Customer中的Property的名称。我不相信有任何代码可以可靠地执行此操作。考虑:
Field myFirstNameField = myCustomer.FirstName;
myFirstNameField.Value = "X";
这里的物业名称应该是什么? Field的单个实例现在在两个单独的范围内有两个标识符。也许有一种方法可以撤回引用你的对象的标识符列表(尽管你可以,我不知道如何),但你会如何选择你想要的?这就是我建议使用构造函数“注入”属性名称的原因,如果你想以这种方式实现PropertyChangeNotification。
答案 4 :(得分:0)
有办法做你想做的事,但没有一个是特别优雅或万无一失。
例如,当您检测到错误条件时,可以在Field类中遍历调用堆栈,并假设始终通过从对象中检索Field来调用它。这可能并非总是如此......
或者,您可以使用LINQ表达式在初始化时将字段名称传递给Field对象。 Marc Gravell有一个technique that's described here。在你的情况下,这可能也不完美。
最终,你可能会更简单,更简单的实现。
最后,您应该考虑这是否真的是重复的情况,而不是不必要的耦合。为什么从代码中发出的消息应该与其中的属性或方法的名称相关联?如果您需要在未来的某个时间点将代码国际化,该怎么办?如果客户希望消息更清晰或更紧密地与业务领域保持一致,那么您是否愿意重构代码以适应此类更改?
答案 5 :(得分:0)
这是解决您尝试做的事情的方法:
Customer c1 = new Customer();
c1.FirstName.Value = "Me";
Type t = c1.GetType();
MemberInfo[] infos = t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (MemberInfo info in infos)
{
if (info.MemberType.ToString().Contains("Field"))
{
Console.WriteLine("Found member {0} of type {1}", info.Name, info.MemberType);
}
}
但我同意使用模板解决方案会更好。
答案 6 :(得分:0)
你想要做的事情实际上并不是你设计的方式。它似乎应该是,但实际上这两个类之间并没有多少关系。
我只想添加name属性并将其更改为泛型类,因为行走堆栈在所有中效率不高,但是为了尝试回答您的问题,您可以执行此操作以获得大部分内容要求......
如果你包含了字段类并且创建了实际类型的属性,那么你可以在栈中获取属性名称(如果你添加了许多其他人建议的变化以使它成为泛型类,那么它会使它成为更好的解决方案,因为你不需要投射任何东西。)
如果你写了这样的客户类:
public class Customer
{
private Field _firstName = new Field(typeof(string), 20);
public string FirstName
{
get
{
return _firstName.Value as string;
}
set
{
_firstName.Value = value;
}
}
}
这将允许您遍历堆栈以获取字段类中的调用方法名称(在本例中为属性)
string name = "<unknown>";
StackTrace st = new StackTrace();
name = st.GetFrame(1).GetMethod().Name;
名称现在具有值
set_FirstName
因此您只需将set_ off除去以获取属性名称,但如果您使用的是INotifyPropertyChanged事件,则仍然会遇到问题,因为发送方将是Field对象而不是客户对象。
答案 7 :(得分:0)
在我看来,是的,可能有一种方法可以使用反射来获取这些信息,但我不确定这是一种万无一失的方法,并且可能会让您在以后更加麻烦的调试阶段。它似乎过分......聪明。
在我看来,根据我过去实施的内容,Fredrik和Micahtan指出了你正确的方向。 Field类应该实现一个Name属性,在实例化时设置。我可以指出这是一个好主意的一个原因是,这是微软做同样事情的方式。如果查看任何可视化设计器的生成代码,控件将实现由设计器设置的Name属性。如果有一种万无一失的方法可以做到这一点,你必须相信这样做。
使用Name属性的另一个好处是,它允许您为您的属性提供“英语”翻译。即“First Name”而不是“FirstName”,这是一种更友好的用户界面方法,并将用户与您的实现分离(使“decouple”这个词重载)。
答案 8 :(得分:0)
我现在确信我无法从这里到达那里。在我看来,如果有一个类型为Field的属性,那么询问Field你的属性名称应该很简单。很明显,我会试图过于聪明,以避免一点点工作。我将在构造函数中添加属性名称。该字段永远不会在一个类之外创建,因此如果属性名称更改,则必须在两个位置进行更改可能不方便。我并不担心让属性名称客户友好或全球化,因为错误是针对其他程序员试图将太长的字符串(或转换为字符串的数字太大)放入字段中。我喜欢泛型建议,但还没有弄清楚如何使用它,仍然能够将所有字段放入列表中。最后,我想按特定顺序迭代列表,并构建一个类似于field1 = value1 | field2 = value2 | etc的单个字符串。