Just as an exercise, I thought I’d examine common creation patterns in C# and how operator overloading can expand these.
Typically, if the parameters passed into a class constructor are not valid and thus cannot create an instance of itself, an Exception is thrown. ArgumentNullException is most common when a parameter is null and shouldn’t be. Also common are ArgumentExceptions and FormatExceptions, especially when a constructor takes in a string that must abide by a syntax and doesn’t.
public class Expression
{
public Expression(string pattern)
{
if (Expression.IsInvalidPattern(pattern))
{
throw new ArgumentException("pattern is invalid");
}
// Do something
}
}
Then a try/catch block is used to catch Exceptions when instantiating the type:
Expression expression;
try
{
expression = new Expression(pattern);
}
catch(ArgumentException)
{
// Abort
}
An alternative would be to simply indicate that the instantiated class is in an invalid state:
public class Expression
{
public readonly bool IsInvalid = false;
public Expression(string pattern)
{
if (Expression.IsInvalidPattern(pattern))
{
this.IsInvalid = true;
return;
}
// Do something
}
}
The invalid state is readonly so that it can only be set by the constructor, and is then checked for before performing any operations with the new Expression:
Expression expression = new Expression(pattern);
if (expression.IsInvalid)
{
// Abort
}
The Parse/TryParse pattern is also common. Instead of an Exception, readonly field, or get-only property, a static TryParse method is used that returns true or false, depending on whether the type can be created:
public class Expression
{
public static bool TryParse(string pattern,
out Expression expression)
{
if (Expression.IsInvalidPattern(pattern))
{
expression = null;
return false;
}
expression = new Expression(pattern);
return true;
}
}
TryParse() is then used as in this example:
Expression expression;
if (!Expression.TryParse(pattern, out expression))
{
// Abort
}
Another pattern which I haven’t seen used allows an invalid object to emulate a null reference. There are better solutions than this, which is why it isn’t used, but by using operator overloading, it can be accomplished nonetheless.
public abstract class Errorable
{
protected bool isError = false;
public static bool operator ==(Errorable left, object right)
{
if (right == null)
{
return left.isError;
}
return (left.GetHashCode() == right.GetHashCode());
}
public static bool operator !=(Errorable left, object right)
{
if (right == null)
{
return !left.isError;
}
return (left.GetHashCode() != right.GetHashCode());
}
}
public class Expression : Errorable
{
public Expression(string pattern)
{
if (Expression.IsInvalidPattern(pattern))
{
base.isError = true;
return;
}
// Do something
}
}
An Expression can then be evaluated to test for null, although we know that “expression” really isn’t a null referenced, which is one reason why this pattern is probably a bad idea.
Expression expression = new Expression(pattern);
if (expression == null)
{
// Abort
}
Lastly, overloading the ! operator allows us to evaluate an object as fasly, which other languages (e.g. JavaScript) do by default:
public abstract class Errorable
{
protected bool isError = false;
public static bool operator !(Errorable errorable)
{
if (errorable == null || errorable.isError);
}
}
public class Expression : Errorable
{
public Expression(string pattern)
{
if (Expression.IsInvalidPattern(pattern))
{
base.isError = true;
return;
}
// Do something
}
}
To test an Expression as invalid, the ! operator is used:
Expression expression = new Expression(pattern);
if (!expression)
{
// Abort
}
The first two patterns are common, and probably why the last two aren’t used. Generally, if there’s already a good, well-established pattern, there’s no reason to try something else. The alternative shown creation patterns were simply an exercise in operator overloading.