Friday, February 26, 2010

Improving C# properties

(I apologize to those of you who have no idea what I'm talking about. We will return to our regularly scheduled program of communist and atheist rants tomorrow.)

(Permission is hereby granted by the author for any person or organization to make use of the contents of this post only, for any purpose, including derivative work and for-profit and commercial use, with or without attribution or compensation.)

Properties in C# are powerful and useful enough that they ought to be used, but they are not powerful enough to always reduce code, make implementation simpler and eliminate the possibility of bugs.

Ideally, all properties should implement an interface:
public interface IProperty <TProperty, TParent>
{
    TProperty get (TParent parent);
    void set (TParent parent, TProperty value);
}
with the declaration, access and assignment of properties "syntatic sugar" for this interface. For example:
public class MyClass {
   private int _myField;
   public int MyProperty { get { return _myField; } set { _myField = value; } }
}

public void SomeFunction (MyClass myInstance)
{
   if ( myInstance.MyProperty < 0 )
      myInstance.MyProperty = 0;
}
would be an idiom for
public class MyClass {
   private int _myField;
   public struct MyProperty_definition : IProperty <int, MyClass> {
      int get (MyClass parent) { return parent._myField; }
      void set (MyClass parent, int value) { parent._myField = value; }
   }

   public MyProperty_definition MyProperty;
}

public void SomeFunction (MyClass myInstance)
{
   if ( myInstance.MyProperty.get (myInstance) < 0 )
      myInstance.MyProperty.set (myInstance, 0);
}
This specification allows us to move the backing field into the property, either explicitly:
public class MyClass {
   public struct MyPropertyType : IProperty <int, MyClass> {
      private int _backingField;
      public int get (MyClass parent) { return _backingField; }
      public void set (MyClass parent, int value) { _backingField = value; }
   }

   public MyPropertyType MyProperty;
}
or implicitly:
public class MyClass {
   int MyProperty { 
      private int _backingField;
      public get { return _backingField; }
      public set { _backingField = value; }
   }
}
Making properties use full struct/class semantics would allow us to do nifty stuff like creating meta-properties. For example:
public struct NotifyProperty <TProperty, TParent> : IProperty <TProperty, TParent>
where TParent : INotifyPropertyChanged
{
   private TProperty _backingField;
   private string _propertyName;

   public NotifyProperty (string name) { _propertyName = name; }

   public TProperty get (TParent parent) { return _backingField; }
   public TProperty set (TParent parent, TProperty value)
   {
      _backingField = value;
      if ( parent.PropertyChanged != null )
         parent.PropertyChanged (parent, new PropertyChangedEventArgs(_propertyName);
   }
}

public class MyClass : INotifyPropertyChanged
{
   public NotifyProperty <int, MyClass> MyIntProperty = 
      new NotifyProperty <int, MyClass> ("MyIntProperty");
   public NotifyProperty <string, MyClass> MyIntProperty = ...
}   
Further syntactic sugar might also automatically supply the property name to the instance, although the integration of reflection into C# syntax is not nearly complete.

Finally, auto-implemented properties need to allow the programmer to auto-implement the initialization, using something like an "init new;" element. Instead of this:
public class MyClass
{
   public MyOtherClass MyAutoProperty { get; private set; }

   public MyClass ()
      { MyAutoProperty = new MyOtherClass (); }
}
We could have this:
public class MyClass
{
   public MyOtherClass MyAutoProperty { init new; get; private set; }

   public MyClass ()
      { }
}
Just to keep things simple, the auto-implemented property would probably have to have a backing field of class type with a default (parameterless) constructor. Perhaps an "init default;" or "init null;" element could also be implemented to explicitly show the auto-implemented property is initialized with the default null value.

6 comments:

  1. You're giving me flashbacks to my previous life... reading up on C# was the last thing I did before I went full-lawyer. Heh.

    ReplyDelete
  2. Hey! You don't write code, and I won't practice law, OK? :p

    ReplyDelete
  3. Could you translate that into Ruby? Thank you very much. :-)

    ReplyDelete
  4. I was having this thought tonight while staring at someone's code, and the mountainous constructors that do nothing more than initialize twenty properties to new, or read in a simple data structure from an entity class. I would have to agree that it would greatly simplify things if one could just specify an init function in the property declaration.

    ReplyDelete
  5. Better to declare a private field and have the property reference it:

    private MyType _field = new MyType ();
    public MyType Property { get { return _field; } }

    is better than

    public MyClass () { Property = new MyType (); }
    public MyType Property { get; private set; }

    ReplyDelete

Please pick a handle or moniker for your comment. It's much easier to address someone by a name or pseudonym than simply "hey you". I have the option of requiring a "hard" identity, but I don't want to turn that on... yet.

With few exceptions, I will not respond or reply to anonymous comments, and I may delete them. I keep a copy of all comments; if you want the text of your comment to repost with something vaguely resembling an identity, email me.

No spam, pr0n, commercial advertising, insanity, lies, repetition or off-topic comments. Creationists, Global Warming deniers, anti-vaxers, Randians, and Libertarians are automatically presumed to be idiots; Christians and Muslims might get the benefit of the doubt, if I'm in a good mood.

See the Debate Flowchart for some basic rules.

Sourced factual corrections are always published and acknowledged.

I will respond or not respond to comments as the mood takes me. See my latest comment policy for details. I am not a pseudonomous-American: my real name is Larry.

Comments may be moderated from time to time. When I do moderate comments, anonymous comments are far more likely to be rejected.

I've already answered some typical comments.

I have jqMath enabled for the blog. If you have a dollar sign (\$) in your comment, put a \\ in front of it: \\\$, unless you want to include a formula in your comment.