Another solution is to take advantage of the virtual nature of DefaultValueAttribute and derive your own. Below are some examples with non-constant-able types.
Example Usage:
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; }
};
Here are the definitions of those attributes:
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);
};
Gotchas to be aware of
Be careful when defining your own DefaultValueAttribute. Most importantly, learn a bit about how it will get used before creating it. For something like a code generator, the above is likely ok. If, however, you are using it with Newtonsoft Json or something that will mostly only use it to compare values, then you may want the value within to be more constant, in order to save time and not re-create the object every time.
For a situation where you want the value to not be re-created every time, you may do something more like:
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);}
Where the attribute DefaultValueStatic is defined as:
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}'");
}
};
The above version will ensure that the default value is not re-created. This can be useful with something like Newtonsoft json, where the setters won't be called often, but the default comparison will.
Again, just make sure you know a bit of how the attribute will be used.
["a","b"]is not valid syntax.new[] { "a", "b" }is.new[] { "a", "b" }is not valid C# syntax either, or is it? I mean, without an element type of the array?