属性getter中使用的表达式内部递归



如何避免属性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构造,如下所示。它的工作原理如下:

  1. 如果用户没有为属性指定值,那么在调用属性getter时,它将返回硬编码的默认值
  2. 如果用户为属性指定了一个值,则该值将首先用作键,以尝试从资源中检索相应的字符串值。如果找不到资源,则将其用作属性的值,前提是它不为空,否则它将返回硬编码的默认值

请注意,属性属性值的这种双重用途导致了相当脆弱的设计解决方案。如果找不到键为"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;
    }
}

最新更新