I try to select the syntax for initializing the attached properties in my CsConsoleFormat library. Attached properties perform the same role as WPF.
Here are the options drawn. For various reasons, none like it.
Indexer (ak.a dictionary initializer):
var a = new Element { Oops = 1, I = 2, Did = 3, It = 4, Again = 5, [Element.FooProperty] = 2.1, [Element.BazProperty] = Guid.NewGuid() }; a.WritePropertyValues();Pros:
- It looks pretty, almost like initializing normal properties.
- Combined with object initializer at the same level.
- It works not only initialization, but also reading and writing.
- Avalonia UI uses similar syntax for binding.
Minuses:
- One, but bold: indexers can not be generalized, so no type checking, everywhere
object. And not only verification, but also conversion is lost.
Collection initializer with two arguments:
var b = new Element { Oops = 1, I = 2, Did = 3, It = 4, Again = 5, Values = { { Element.FooProperty, 2 }, { Element.BarProperty, "Hello!" } } }; b.WritePropertyValues();Pros:
- Strongly typed.
- Can be combined with an indexer for reading and writing.
Minuses:
- Initializers of collections and objects are not combined, it is necessary to allocate a separate level.
- It looks doubtful because of the forest of curly braces: at the end of their expression there are already three of them.
- If you add an indexer for reading and writing, you get a jumble: in one place it is typed, in the other not.
A single-argument collection initializer in conjunction with an operator:
var c = new Element { Oops = 1, I = 2, Did = 3, It = 4, Again = 5, Values = { Element.FooProperty == 10, Element.BazProperty == Guid.NewGuid() } }; c.WritePropertyValues();Pros:
- Strongly typed.
- The syntax is moderately brief and moderately pleasant.
- Can be combined with an indexer for reading and writing.
Minuses:
- The assignment operator does not overload, you have to overload the lowest priority binary operator, and it is already high enough to spoil some expressions (
Element.BoolProperty == a == b). - Equality for imitation assignment is not the most logical move. I do not want to be in the role of the "inventor" of the
>>operator in C ++. However, in Avalonia UI allow to play with the operators, why not me. - If you add an indexer for reading and writing, you get a jumble: in one place it is typed, in the other not.
Good old fluent :
var d = new Element { Oops = 1, I = 2, Did = 3, It = 4, Again = 5 } .Set(Element.FooProperty, 1337).Set(Element.BarProperty, "World!"); d.WritePropertyValues();Pros:
- Strongly typed.
- Can be combined with the symmetric
Getmethod. - If combined with
Get, then the operation is symmetrical and uniform.
Minuses:
- The syntax is horrible and terrible, if you want to use both ordinary and attached properties (to transfer all properties to a fluent is not an option).
Code that implements all the syntaxes described above:
internal class Program { private static void Main() { var a = new Element { Oops = 1, I = 2, Did = 3, It = 4, Again = 5, [Element.FooProperty] = 2.1, [Element.BazProperty] = Guid.NewGuid() }; a.WritePropertyValues(); var b = new Element { Oops = 1, I = 2, Did = 3, It = 4, Again = 5, Values = { { Element.FooProperty, 2 }, { Element.BarProperty, "Hello!" } } }; b.WritePropertyValues(); var c = new Element { Oops = 1, I = 2, Did = 3, It = 4, Again = 5, Values = { Element.FooProperty == 10, Element.BazProperty == Guid.NewGuid() } }; c.WritePropertyValues(); var d = new Element { Oops = 1, I = 2, Did = 3, It = 4, Again = 5 } .Set(Element.FooProperty, 1337).Set(Element.BarProperty, "World!"); d.WritePropertyValues(); Console.ReadKey(); } } internal class Element { public static readonly Property<int> FooProperty = Property.Register("Foo", 1); public static readonly Property<string> BarProperty = Property.Register("Bar", "a"); public static readonly Property<Guid> BazProperty = Property.Register("Baz", Guid.Empty); private readonly Dictionary<Property, object> _properties = new Dictionary<Property, object>(); public int Oops { get; set; } public int I { get; set; } public int Did { get; set; } public int It { get; set; } public int Again { get; set; } public Values Values { get; } public Element() { Values = new Values(this); } public object this[Property property] { get => _properties.TryGetValue(property, out object value) ? value : property.DefaultValueUntyped; set => _properties[property] = value; } public Element Set<T>(Property<T> prop, T v) { _properties[prop] = v; return this; } public void WritePropertyValues() { foreach (KeyValuePair<Property, object> property in _properties) Console.WriteLine($"{property.Key.Type.Name} {property.Key.Name} = {property.Value} (default: {property.Key.DefaultValueUntyped})"); Console.WriteLine(); } } internal class Values : IEnumerable { private readonly Element _element; public Values(Element element) => _element = element; public void Add<T>(Property<T> prop, T v) => _element[prop] = v; public void Add<T>(PropertyValue<T> pv) => _element[pv.Property] = pv.Value; IEnumerator IEnumerable.GetEnumerator() => null; } internal abstract class Property { public string Name { get; } public object DefaultValueUntyped { get; } public abstract Type Type { get; } protected Property(string name, object defaultValueUntyped) { Name = name; DefaultValueUntyped = defaultValueUntyped; } public static Property<T> Register<T>(string name, T defaultValue) => new Property<T>(name, defaultValue); } internal class Property<T> : Property { public T DefaultValue => (T)DefaultValueUntyped; public override Type Type => typeof(T); internal Property(string name, T defaultValue) : base(name, defaultValue) { } public static PropertyValue<T> operator ==(Property<T> property, T value) => new PropertyValue<T>(property, value); public static PropertyValue<T> operator !=(Property<T> property, T value) => default; } internal struct PropertyValue<T> { public Property Property { get; set; } public T Value { get; set; } public PropertyValue(Property property, T value) { Property = property; Value = value; } } Perhaps, I am losing some more convenient way of seeing? Are there any other alternatives? Preferably strongly typed and with short syntax.
Opinions about the options described above are also welcome.