【问题标题】:How do I declare a DefaultValue attribute whose value is an array of strings?如何声明其值为字符串数组的 DefaultValue 属性?
【发布时间】:2012-09-10 19:47:57
【问题描述】:

我一直在代码生成器中使用 DefaultValue 属性,它从模式中编写 C# 类定义。

我被困在架构中的属性是字符串数组的地方。

我想在我的 C# 中写这样的东西:

[DefaultValue(typeof(string[]), ["a","b"])]
public string[] Names{get;set;}

但这不会编译。

有什么方法可以成功声明字符串数组的默认值属性?

【问题讨论】:

  • ["a","b"] 语法无效。 new[] { "a", "b" } 是。
  • @Jon:事实上,new[] { "a", "b" } 也不是有效的 C# 语法,不是吗?我的意思是,没有数组的元素类型?
  • @O.R.Mapper: Implicitly typed arrays.
  • @Jon: 有趣的新功能 :-) 我们还没有使用 .NET 4.5(事实上,我们的大多数项目仍然停留在 3.5 工具集中),所以我还没有遇到这个。
  • @O.R.Mapper:不是很新,它从 C# 3.0 就已经存在了。

标签: c# .net attributes default-value


【解决方案1】:

另一个解决方案是利用DefaultValueAttribute 的虚拟特性并派生出你自己的。下面是一些非常量类型的示例。

用法示例:

public class Foo
{
    [DefaultValueNew( typeof(List<int>), new int[]{2,2} )]
    public List<int> SomeList { get; set; }

    // -- Or --
    public static List<int> GetDefaultSomeList2() => new List<int>{2,2};

    [DefaultValueCallStatic( typeof(Foo), nameof(GetDefaultSomeList2) )]
    public List<int> SomeList2 { get; set; }
};

以下是这些属性的定义:

  public class DefaultValueNewAttribute : DefaultValueAttribute
  {
    public Type Type { get; }
    public object[] Args { get; }

    public DefaultValueNewAttribute(Type type, params object[] args)
      : base(null)
    {
      Type = type;
      Args = args;
    }

    public override object? Value => Activator.CreateInstance(Type, Args);
  };

  public class DefaultValueCallStaticAttribute : DefaultValueAttribute
  {
    public Type Type { get; }
    public string Method { get; }

    public DefaultValueCallStaticAttribute(Type type, string method)
      : base(null)
    {
      Type = type;
      Method = method;
    }

    public override object? Value => Type.GetMethod(Method, BindingFlags.Public | BindingFlags.Static).Invoke(null, null);
  };

需要注意的问题

定义自己的DefaultValueAttribute 时要小心。最重要的是,在创建它之前先了解一下如何使用它。对于像代码生成器这样的东西,上面可能没问题。但是,如果您将它与 Newtonsoft Json 或主要仅使用它来比较值的东西一起使用,那么您可能希望其中的值更加恒定,以节省时间而不是每次都重新创建对象。

对于您不希望每次都重新创建值的情况,您可以执行以下操作:

  public static readonly List<int> DefaultAges = new List<int>{2,2};
  private List<int> __ages = new List<int>(DefaultAges);
  [DefaultValueStatic( typeof(List<int>), nameof(DefaultAges) )]
  public List<int> Ages { get => __ages; set {__ages = new List<int>(value);}

其中属性DefaultValueStatic定义为:

  public class DefaultValueStaticAttribute : DefaultValueAttribute
  {
    public DefaultValueStaticAttribute(Type type, string memberName) : base(null)
    {
      foreach (var member in type.GetMember( memberName, BindingFlags.Static | BindingFlags.Public ))
      {
        if (member is FieldInfo fi) { SetValue(fi.GetValue(type)); return; }
        if (member is PropertyInfo pi) { SetValue(pi.GetValue(type)); return; }
      }
      throw new ArgumentException($"Unable to get static member '{memberName}' from type '{type.Name}'");
    }
  };

以上版本将确保不会重新创建默认值。这对于像 Newtonsoft json 这样的东西很有用,其中 setter 不会经常被调用,但默认比较会。

同样,请确保您了解该属性的使用方式。

【讨论】:

    【解决方案2】:

    你可以试试

    [DefaultValue(new string[] { "a", "b" })]
    

    当你想传递一个新的字符串数组时,你必须实例化它——这由new string[] 完成。 C# 允许在大括号中跟随数组初始内容的初始化列表,即{ "a", "b" }


    编辑:正如Cory-G 正确的pointed out,您可能希望确保您的实际实例不会收到存储在DefaultValue 属性中的数组实例。否则,对该实例的更改可能会影响整个应用程序的默认值。

    相反,您可以使用属性的 setter 来复制分配的数组。

    【讨论】:

    • @MichaelBishop:您对问题的评论暗示您正在使用System.ComponentModel.DefaultValueAttribute? (这在问题中并不清楚;从问题中,我假设它是其他一些因此命名的属性类型,它提供了 (Type, object) 构造函数。)如果是这样,您可以尝试使用 this constructor 并且不传递任何 Type .
    • [DefaultValue(new string[] { "a", "b" })] 这行得通。谢谢!我看到了特定类型的所有 DefaultValueAttribute 构造函数,甚至没有意识到我可以传递一个通用对象。
    • 你会如何使用通用列表来做到这一点?像这样作为默认值new List&lt;CommonModels.Contact&gt;
    • 我在寻找更通用的语法时遇到了这个答案。这似乎不适用于自定义类/结构定义,例如[DefaultValue(new Whatever[] {})](我想要空数组默认)
    • 这个解决方案对于不知情的人来说似乎有潜在的危险。很确定这将返回一个共享引用,这意味着如果您完全更改数组,那么该属性所持有的默认值以及所有获得该值的内容都将被更改。如果使用它,可能会在你的设置器或其他东西中放一些“复制数组”代码。
    猜你喜欢
    • 2020-04-23
    • 2014-03-24
    • 2020-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-22
    • 2011-04-18
    相关资源
    最近更新 更多