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

时间:2015-04-05 21:22:22

标签: c# .net function lambda expression

如何避免在属性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。 您能否告诉我如何避免这种行为,但这个解决方案的简单性呢?

编辑:此类的一个部分是为按钮提供标题 - 用户可以设置哪些资源管理器使用多语言标题。 在上面的示例中,按钮选择从资源翻译,对于更改按钮,使用默认文本&#34;更改...&#34;并删除按钮标题&#34;销毁这个@&amp;#!&#34;。因此,如果用户没有指定属性值,则应用程序使用默认文本,否则尝试在资源中查找文本,如果找到匹配,则使用来自资源的文本,否则使用用户设置。

[Required]
[Upload(ResourceType = typeof(Resource), Select = "UploadSelect", Remove = "Destroy this @&#!")]
public HttpPostedFileBase Logo { get; set; }

3 个答案:

答案 0 :(得分:2)

如果没有明确设置,您尝试实现的目的似乎是初始化这些属性。你这样做的方式是行不通的。

m => m.Remove类型表达式将导致在无限递归中再次调用属性getter,直到发生堆栈溢出。

您可以使用lazy构造,如下图所示。它的工作原理如下:

  1. 如果用户没有为属性指定值,则在调用属性getter时,它将返回硬编码的默认值。
  2. 如果用户为属性指定了值,则该值将首先用作键,以尝试从资源中检索相应的字符串值。如果找不到资源,则将其用作属性的值,前提是它不为空,否则将返回硬编码的默认值。
  3. 请注意,属性属性值的这种双重用途会导致相当脆弱的设计解决方案。如果找不到具有键“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;
        }
    }
    

答案 1 :(得分:1)

我非常倾倒。它根本不会创建新的实例;我问的那一刻,我回答了我的问题。

return GetResourceText(m => m.Select, "Select...");是递归但没有结束,但return GetResourceText(m => m._select, "Select...");不是,因为我没有再次调用方法GetResourceText。

对不起男孩们的愚蠢问题。

答案 2 :(得分:1)

如果您希望在使用属性名称时保持编译时的安全性,请稍微修改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;
    }
}