如何避免属性getter中的递归调用?这是我的简单代码
public class UploadAttribute : Attribute
{
private Type _resourceType;
private string _select;
private string _change;
private string _remove;
public Type ResourceType
{
get { return _resourceType; }
set { _resourceType = value; }
}
public string Select
{
get { return GetResourceText(m => m.Select, "Select..."); }
set { _select = value; }
}
public string Change
{
get { return GetResourceText(m => m.Change, "Change..."); }
set { _change = value; }
}
public string Remove
{
get { return GetResourceText(m => m.Remove, "Remove"); }
set { _remove = value; }
}
private string GetResourceText(Expression<Func<UploadAttribute, string>> expression, string @default)
{
var value = expression.Compile().Invoke(this); // here .net is creating new UploadAttribute instance and use it for expression fnc
var result = value ?? @default;
if (_resourceType != null && !string.IsNullOrEmpty(value))
{
ResourceManager rm = new ResourceManager(_resourceType);
try
{
result = rm.GetString(value);
}
catch
{
// if string wasn't found in resource file than use what user specify; don't by big brother.
}
}
return result;
}
}
但若您查看方法GetResourceText,那个么有一行需要编译并调用表达式来获取给定属性的值。不幸的是,此操作会创建UploadAttribute的新实例。在那一刻,如果我没有弄错的话,.net遍历所有属性并调用getter,在getter中,.net编译并调用表达式来获取值或给定的属性,一次又一次地调用StackOverlowException。你能建议我如何避免这种行为,但要使用简单的解决方案吗?
编辑:这个类的作用是为按钮提供标题-用户可以从资源管理器中设置使用多语言标题的内容。在上面的例子中是按钮从资源中选择翻译,对于更改按钮使用默认文本"更改…",对于删除按钮标题"销毁这个@&#!"。所以,若用户并没有指定属性值,应用程序将使用默认文本,否则将尝试在资源中查找文本,若找到匹配,则使用资源中的文本,否则使用用户设置的文本。
[Required]
[Upload(ResourceType = typeof(Resource), Select = "UploadSelect", Remove = "Destroy this @&#!")]
public HttpPostedFileBase Logo { get; set; }
如果这些属性没有明确设置,那么您想要实现的似乎是通过某种方式初始化它们。你这样做是行不通的。
m => m.Remove
类型表达式将导致在无限递归中再次调用属性getter,直到发生堆栈溢出。
您可以使用lazy
构造,如下所示。它的工作原理如下:
- 如果用户没有为属性指定值,那么在调用属性getter时,它将返回硬编码的默认值
- 如果用户为属性指定了一个值,则该值将首先用作键,以尝试从资源中检索相应的字符串值。如果找不到资源,则将其用作属性的值,前提是它不为空,否则它将返回硬编码的默认值
请注意,属性属性值的这种双重用途导致了相当脆弱的设计解决方案。如果找不到键为"UploadSelect"的资源,它将成为按钮上的标题。
public class UploadAttribute : Attribute
{
private static readonly string kSelectDefaultCaption = "Select...";
private static readonly string kChangeDefaultCaption = "Change...";
private static readonly string kRemoveDefaultCaption = "Remove...";
private Type _resourceType;
private Lazy<string> _select = new Lazy<string>(() => kSelectDefaultCaption);
private Lazy<string> _change = new Lazy<string>(() => kChangeDefaultCaption);
private Lazy<string> _remove = new Lazy<string>(() => kRemoveDefaultCaption);
public Type ResourceType
{
get { return _resourceType; }
set { _resourceType = value; }
}
public string Select
{
get { return _select.Value; }
set { _select = new Lazy<string>(() => GetResourceText(value, kSelectDefaultCaption)); }
}
public string Change
{
get { return _change.Value; }
set { _change = new Lazy<string>(() => GetResourceText(value, kChangeDefaultCaption)); }
}
public string Remove
{
get { return _remove.Value; }
set { _remove = new Lazy<string>(() => GetResourceText(value, kRemoveDefaultCaption)); }
}
private string GetResourceText(string key, string @default)
{
// initialize to default.
var result = @default;
if (_resourceType != null && !string.IsNullOrEmpty(key))
{
// initialize to the value of the key,
// that could be a user supplied string literal
result = key;
// attempt to retrieve it from the resources.
ResourceManager rm = new ResourceManager(_resourceType);
try
{
result = rm.GetString(key);
}
catch
{
// could not retrieve key, using the key value as the result.
}
}
return result;
}
}
我真是个废物。它根本不会创建新的实例;我一问就立刻回答了我的问题。
return GetResourceText(m => m.Select, "Select...");
是无尾递归,但return GetResourceText(m => m._select, "Select...");
不是,因为我不会再次调用方法GetResourceText。
我很抱歉孩子们问了这个愚蠢的问题。
如果您希望在使用属性名称时保持编译时的安全性,请对Alex的答案进行轻微修改:
public class UploadAttribute : Attribute
{
private Type _resourceType;
private Lazy<string> _select;
private Lazy<string> _change;
private Lazy<string> _remove;
UploadAttribute()
{
_select = new Lazy<string>(() => GetResourceText(m => m.Select, "Select..."));
_change = new Lazy<string>(() => GetResourceText(m => m.Change, "Change..."));
_remove = new Lazy<string>(() => GetResourceText(m => m.Remove, "Remove..."));
}
public Type ResourceType
{
get { return _resourceType; }
set { _resourceType = value; }
}
public string Select
{
get { return _select.Value; }
set { _select = new Lazy<string>(() => value); }
}
public string Change
{
get { return _change.Value; }
set { _change = new Lazy<string>(() => value); }
}
public string Remove
{
get { return _remove.Value; }
set { _remove = new Lazy<string>(() => value); }
}
private string GetResourceText(Expression<Func<UploadAttribute, string>> expression, string @default)
{
var result = @default;
var memberExpression = expression.Body as MemberExpression;
if (_resourceType != null && memberExpression != null)
{
ResourceManager rm = new ResourceManager(_resourceType);
try
{
result = rm.GetString(memberExpression.Member.Name);
}
catch
{
// if string wasn't found in resource file than use what user specify; don't by big brother.
}
}
return result;
}
}