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

Recursion inside expression used in property 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。 你能告诉我如何避免这种行为但又简单的解决方案吗?

编辑: 这个 class 的目的是为按钮提供标题 - 用户设置可以使用资源管理器中的多语言标题。 在上面的示例中,按钮 Select 从 Resources 翻译而来,Change 按钮使用默认文本 "Change...",Remove 按钮标题 "Destroy this @&#!"。因此,如果用户未指定 属性 值,应用程序将使用默认文本,否则尝试在资源中查找文本,如果找到匹配则使用资源中的文本,否则使用用户设置的文本。

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

看来您想要实现的是在未明确设置这些属性的情况下通过某种方式对其进行初始化。你这样做的方式是行不通的。

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

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

  1. 如果用户没有为 属性 指定值,它将在 属性 getter 被调用时 return hard-coded 默认值.
  2. 如果用户为 属性 指定一个值,该值将首先用作尝试从资源中检索相应字符串值的键。如果未找到资源,则将其用作 属性 的值,前提是它不为空,否则将退回到 hard-coded 默认值。

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