public class Foo { private string _bar; }Now we want to add an accessor aka the getter:
public class Foo { public string Bar { get { return _bar; } private string _bar; }This has been the easy part. There is not really a lot that can go wrong.
It starts to become more interesting when you add a modifier aka a setter. In it's most simple form you could write:
public class Foo { public string Bar { get { return _bar; } set { _bar = value; } private string _bar; }Easy you think. But hang on. There is more to it. You may want to decide whether it is acceptable to pass in a null reference or not. Whether you allow for null or not is a decision that should be made based on a number of factors. One way to find out is to look at all the places in your code where the getter is used. What would happen to that code if null would be returned? For example:
Foo foo = new Foo(); ... if( foo.Bar.Length > 25 ) { ... } ...In that case if null was returned this piece of code would crash. You could fix this issue by checking for nullness:
Foo foo = new Foo(); ... if( foo.Bar != null && foo.Bar.Length > 25 ) { ... } ...This certainly work but you pay the price of a slightly less readable code. In addition you may have to have this in a lot of places. So in essence you may decide that the property Foo.Bar doesn't allow for null values. The code for class Foo would then look as this:
public class Foo { public string Bar { get { return _bar; } set { if( value != null ) { _bar = value; } } private string _bar; }This clearly provides the benefit of Foo.Bar never being null since upon initialization _bar will be initialized with string.Empty or "".
But again this comes at a price. The setter simply swallows the attempt to set Foo.Bar to null. This might be desirable. I personally prefer that a class doesn't swallow incorrect things but instead fails fast. In this particular case I would want my code to indicated the error by throwing an exception:
public class Foo { public string Bar { get { return _bar; } set { if( value != null ) { _bar = value; } else { throw new ArgumentNullException("value"); } } private string _bar; }You see that although this is a simple property implementation it can already require quite a few decisions to be made and aspects to be considered.
To close off this particular post, I'd like to also bring performance considerations into the picture. What if the setter needs to validate any new value against a remote system such as a service? Let's look at the possible code:
public class Foo { public string Bar { get { return _bar; } set { if( value != null ) { if( _validationService.IsPermitted(value) ) { _bar = value; } else { throw new ArgumentOutOfRangeException("value"); } } else { throw new ArgumentNullException("value"); } } private string _bar; private ValidationService _validationService = new ValidationService(...); }Calling IsPermitted() can be quite expensive. So how to avoid this? Here is one possible solution:
public class Foo { public string Bar { get { return _bar; } set { if( _bar != value ) { if( value != null ) { if( _validationService.IsPermitted(value) ) { _bar = value; } else { throw new ArgumentOutOfRangeException("value"); } } else { throw new ArgumentNullException("value"); } } } private string _bar; private ValidationService _validationService = new ValidationService(...); }With this implementation the validation service is called only if the value has actually changed. Certainly if the set of permitted values is dynamic this implementation would not make the cut. With this post I want to demonstrate that even property that looks like an easy thing to do already requires a lot of considerations. We even touched performance briefly. It is important that we are aware of all of these aspects when implementing and testing such a property. There are more aspects to this but I think I've made my point. Even with simple things like properties there are already a quite a few aspects to consider.
No comments:
Post a Comment
All comments, questions and other feedback is much appreciated. Thank you!