【问题标题】:Recursion inside expression used in property getter属性获取器中使用的递归内部表达式
【发布时间】:2015-04-05 21:22:22
【问题描述】:

如何避免属性 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; }

【问题讨论】:

  • 您似乎将 attributes 属性值设置为两种不同类型的值:(1) 用于从资源中检索标题字符串的资源键,(2) 文字用作标题本身的字符串。这绝对不是一个干净的方法,虽然我自己永远不会选择这样的脆弱设计解决方案,但我想它可以工作。我会根据我对你的目的的新理解来更新我的答案。
  • 对我来说,不清楚这段代码应该做什么。也许你可以澄清你的意图,然后你可能会得到一个满足你要求的答案:)
  • @SebastianBusek 您能否指出任何答案是否足以解决您的问题,或者指出您还有哪些问题。或者,如果您找到了以不同方式自己解决问题的方法,请将其发布为答案。

标签: c# .net function lambda expression


【解决方案1】:

如果没有明确设置这些属性,您似乎想要实现的是有某种方法来初始化这些属性。你这样做的方式是行不通的。

m =&gt; 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;
    }
}

【讨论】:

  • 感谢亚历克斯的回复。如果我对您的代码有很好的理解,那么用户可以设置属性值但他/她无法使用资源中的本地化...但是也许如果您将set { _select = new Lazy&lt;string&gt;(() =&gt; value); } 替换为set { _select = new Lazy&lt;string&gt;(() =&gt; GetResourceText(value, "Select...")); } 它可以工作,是吗?对吗?
  • @SebastianBusek 我不确定我是否理解你的意思。上面的代码所做的是,如果属性值由用户显式设置,则返回类的用户设置的值,如果未设置,则从资源中提取值,它将返回@987654326 @ 如果资源中不存在 key 的值。如果这不是您想要的行为,您能否更新您的问题以指定您想要实现的目标?
  • 您是否打算让这些属性保存用于从资源中检索相应值的资源键的值?
【解决方案2】:

我很垃圾。它根本不会创建新实例;当我问的时候,我马上回答了我的问题。

return GetResourceText(m =&gt; m.Select, "Select..."); 是无限递归,但return GetResourceText(m =&gt; m._select, "Select..."); 不是,因为我不再调用 GetResourceText 方法。

对不起,男孩们提出了愚蠢的问题。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-11
      • 1970-01-01
      • 2018-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多