3
\$\begingroup\$

I've recently got into an habit where I've used extension methods for giving things fluent-like properties - such as the below example or as another example entity.AssertNotNull()

public static class GenericHelper
{
    public static void AssignOrThrowIfNull<T>(this T obj, ref T assignTo, string paramName)
    {
        if (obj == null)
            throw new ArgumentNullException(paramName, "Unallowed null argument of type: " + typeof(T).FullName);
        assignTo = obj;
    }
}

This is then used as follows:

public class SmtpClient

public SmtpServerAddress Server
{
    get { return smtpServer; }
}

private SmtpServerAddress smtpServer;

public SmtpClient(SmtpServerAddress smtpServer)
{
    smtpServer.AssignToOrThrow(ref this.smtpServer, "smtpServer");
}

as opposed to

public SmtpClient(SmtpServerAddress smtpServer)
{
    if(smtpServer == null)
        throw new ArgumentNullException("smtpServer");
}

Do you think this is readable? Is it in any way preferable to regular checks everywhere? The fact that I can't really decide if I like it or not makes me nervous, so I'd like some external input!

UPDATE:

Yes, you made me open my eyes. Hopefully, you will agree with me that the following is a bit more agreeable. I used expressions because I want to include as much information as possible in the exception.

public static class Require
{
    public static void That(Expression<Func<bool>> func)
    {
        if (!func.Compile()())
            throw new ContractException("Contract not fulfilled: " + func.Body, func);
    }
}

Atleast I think this makes it a bit clearer, even though the lambda syntax is not very nice (I don't think smtpServer != null can be inferred to an Expression).

Require.That(() => smtpServer != null);
SmtpServer = smtpServer;

Since I compile the expression each time, there will surely be some performance penalty.

\$\endgroup\$
  • \$\begingroup\$ You can omit usage of Expression<Func<bool>> and replace it usage with Func<T,bool> with captured variables within the scope of the expression, as i did in my extended example. You can combine it to trace the debug output using assertions and you can use standart mechanizms, like trace listeners to get more info on the exception stack; \$\endgroup\$ – Artur Mustafin Oct 17 '11 at 10:50
3
\$\begingroup\$

You might want to look into code contracts instead, which allows you to write:

Contract.Requires<ArgumentNullException>(smtpServer != null);

This expresses clearly what you are trying to do, and has the added advantage of allowing additional static checking by the code contracts tool to check the code that calls your method for possible violations of the contract.

\$\endgroup\$
  • \$\begingroup\$ I agree that code contracts would be nice - the irritating thing is that it is not supported "out of box" with VS2010 - you need to install the pack, even though the classes are part of .net 4 (you just get a real ugly message box otherwise). I ended rewriting a bit of that functionality - see my update if you want to comment on the latest version I wrote! :) \$\endgroup\$ – Max Jul 9 '11 at 13:03
1
\$\begingroup\$

I think that your instincts are right, I don't like it and it seems to be over-architected. The general pattern of checking if an parameter is null and throwing an exception is well established and programmers are used to it - so much so that they can understand it with a quick glance. Your refactored version is unusual and so they'll have to step into the method and look at it.

Extension methods are fanastic on the whole but I don't think that this is a valid use of one.

\$\endgroup\$
  • \$\begingroup\$ You are right - over-architected plus the fact that I abused extension methods was over the line. What do you think of the latest update at the bottom of my post? It might still be over-architected though, but a bit more useful atleast! \$\endgroup\$ – Max Jul 9 '11 at 13:10
  • \$\begingroup\$ I still think it's over-architected. Why not use code contracts instead or just follow the pattern of throwing an exception if a required parameter is null? \$\endgroup\$ – Stuart Leyland-Cole Jul 10 '11 at 20:28
1
\$\begingroup\$

Doesn't make the code shorter.

Doesn't make it clearer to someone who is new to your code.

If there was more logic in the extension method, may be. But in this case - no.

\$\endgroup\$
0
\$\begingroup\$

I whould suggest you to use generics and non-generics version as well as special wrapper to generic class handle exceptions. This is all about using anonymous functions, extension methods and static functions.

I whould like to suggust you to use prototyping using Func<T> and Func<T,U> for non-generic and generic methods respectively;

Additionally, you will not be required to install CodeContracts to use contract requrents. After applying following source code, readability for you code can improve significantly, and have a more elegant look'n'feel:

public SmtpClient(SmtpServerAddress smtpServer, bool dummy1, bool dummy2) 
{ 
    Server = smtpServer 
        .RequireNotNull() 
        .RequireIsNullOrWhitespace((obj) => obj.UNC) 
        .RequireEqualsZero((obj) => obj.UID);
}

or (full sources):

public class SmtpClient
{
    public SmtpServerAddress Server { get; private set; }
    public SmtpClient(SmtpServerAddress smtpServer)
    {
        AssertionContracts.ThrowOnTrue<ArgumentNullException>(() => smtpServer == null);
        Server = smtpServer;
    }
    public SmtpClient(SmtpServerAddress smtpServer, bool dummy1, bool dummy2)
    {
        smtpServer
            .RequireNotNull()
            .RequireIsNullOrWhitespace((obj) => obj.UNC)
            .RequireEqualsZero((obj) => obj.UID);
        Server = smtpServer;
    }  
}

public class SmtpServerAddress 
{
    public string UNC { get; set; }
    public long UID { get; set; } 
}

Source code:

public static class AssertionContracts
{
    private static class Exceptions<U> where U : Exception, new()
    {
        public static T ThrowOnTrue<T>(T obj, Func<T, bool> function, params object[] args)
        {
            if (function(obj) == true)
            {
                Throw(obj, args);
            }
            return obj;
        }
        public static T ThrowOnFalse<T>(T obj, Func<T, bool> function, params object[] args)
        {
            if (function(obj) == false)
            {
                Throw(obj, args);
            }
            return obj;
        }
        public static void Throw<T>(T obj, params object[] args)
        {
            throw CreateException(obj, args);
        }
        private static U CreateException<T>(T obj, params object[] args)
        {
            return (U)Activator.CreateInstance(typeof(U), args);
        }
    }
    public class ContractException : Exception
    {
        public ContractException() : base() { }
        public ContractException(string message) : base(message) { }
        protected ContractException(SerializationInfo info, StreamingContext context) : base(info, context) { }
        public ContractException(string message, Exception innerException) : base(message, innerException) { }
    }
    public static T ThrowOnTrue<T, U>(this T obj, Func<T, bool> function, params object[] args) where U : Exception, new()
    {
        return AssertionContracts.Exceptions<ContractException>.ThrowOnTrue(obj, function);
    }
    public static T ThrowOnFalse<T, U>(this T obj, Func<T, bool> function, params object[] args) where U : Exception, new()
    {
        return AssertionContracts.Exceptions<ContractException>.ThrowOnFalse(obj, function);
    }
    public static void Throw<T, U>(this T obj, Func<T, bool> function, U ex) where U : Exception
    {
        AssertionContracts.Exceptions<ContractException>.Throw(obj, function);
    }
    public static T NoThrowContractException<T>(this T obj, Func<T, bool> function)
    {
        return AssertionContracts.Exceptions<ContractException>.ThrowOnTrue(obj, function);
    }
    public static T ThrowContractException<T>(this T obj, Func<T, bool> function)
    {
        return AssertionContracts.Exceptions<ContractException>.ThrowOnFalse(obj, function);
    }
    public static T AssertTrue<T>(this T obj, Func<T, bool> function)
    {
        Trace.Assert(function(obj) == true);
        return obj;
    }
    public static T AssertFalse<T>(this T obj, Func<T, bool> function)
    {
        Trace.Assert(function(obj) == false);
        return obj;
    }
    public static T AssertIsNullOrEmpty<T>(this T obj, Func<T, string> function)
    {
        Trace.Assert(string.IsNullOrEmpty(function(obj)));
        return obj;
    }
    public static T AssertIsNullOrWhitespace<T>(this T obj, Func<T, string> function)
    {
        Trace.Assert(string.IsNullOrWhiteSpace(function(obj)));
        return obj;
    }
    public static T AssertIsNotNullOrEmpty<T>(this T obj, Func<T, string> function)
    {
        Trace.Assert(!string.IsNullOrEmpty(function(obj)));
        return obj;
    }
    public static T AssertIsNotNullOrWhitespace<T>(this T obj, Func<T, string> function)
    {
        Trace.Assert(!string.IsNullOrWhiteSpace(function(obj)));
        return obj;
    }
    public static T AssertEquals<T, U>(this T obj, Func<T, U> function, U value)
    {
        Trace.Assert(object.Equals(function(obj), value));
        return obj;
    }
    public static T AssertNotEquals<T, U>(this T obj, Func<T, U> function, U value)
    {
        Trace.Assert(!object.Equals(function(obj), value));
        return obj;
    }
    public static T AssertDefault<T, U>(this T obj, Func<T, U> function)
    {
        Trace.Assert(object.Equals(function(obj), default(U)));
        return obj;
    }
    public static T AssertNonDefault<T, U>(this T obj, Func<T, U> function)
    {
        Trace.Assert(!object.Equals(function(obj), default(U)));
        return obj;
    }
    public static T AssertNotNull<T, U>(this T obj, Func<T, U> function)
    {
        Trace.Assert(!object.Equals(function(obj), default(U)));
        return obj;
    }
    public static T AssertNotNull<T>(this T obj)
    {
        Trace.Assert(!object.Equals(obj, default(T)));
        return obj;
    }
    public static T AssertNull<T, U>(this T obj, Func<T, U> function)
    {
        Trace.Assert(object.Equals(function(obj), default(U)));
        return obj;
    }
    public static T AssertNull<T>(this T obj)
    {
        Trace.Assert(object.Equals(obj, default(T)));
        return obj;
    }
    public static T AssertPositive<T>(this T obj, Func<T, int> function)
    {
        Trace.Assert(function(obj) > 0);
        return obj;
    }
    public static T AssertPositive<T>(this T obj, Func<T, long> function)
    {
        Trace.Assert(function(obj) > 0);
        return obj;
    }
    public static T AssertNegative<T>(this T obj, Func<T, int> function)
    {
        Trace.Assert(function(obj) < 0);
        return obj;
    }
    public static T AssertNegative<T>(this T obj, Func<T, long> function)
    {
        Trace.Assert(function(obj) < 0);
        return obj;
    }
    public static T AssertEqualsZero<T>(this T obj, Func<T, int> function)
    {
        Trace.Assert(function(obj) == 0);
        return obj;
    }
    public static T AssertEqualsZero<T>(this T obj, Func<T, long> function)
    {
        Trace.Assert(function(obj) == 0);
        return obj;
    }
    public static T AssertNotEqualsZero<T>(this T obj, Func<T, int> function)
    {
        Trace.Assert(function(obj) != 0);
        return obj;
    }
    public static T AssertNotEqualsZero<T>(this T obj, Func<T, long> function)
    {
        Trace.Assert(function(obj) != 0);
        return obj;
    }
    public static T AssertGreaterThan<T>(this T obj, Func<T, int> function, int value)
    {
        Trace.Assert(function(obj) > value);
        return obj;
    }
    public static T AssertGreaterThan<T>(this T obj, Func<T, long> function, long value)
    {
        Trace.Assert(function(obj) > value);
        return obj;
    }
    public static T AssertLessThan<T>(this T obj, Func<T, int> function, int value)
    {
        Trace.Assert(function(obj) < value);
        return obj;
    }
    public static T AssertLessThan<T>(this T obj, Func<T, long> function, long value)
    {
        Trace.Assert(function(obj) < value);
        return obj;
    }
    public static T AssertGreaterOrEqualsThan<T>(this T obj, Func<T, int> function, int value)
    {
        Trace.Assert(function(obj) >= value);
        return obj;
    }
    public static T AssertGreaterOrEqualsThan<T>(this T obj, Func<T, long> function, long value)
    {
        Trace.Assert(function(obj) >= value);
        return obj;
    }
    public static T AssertLessOrEqualsThan<T>(this T obj, Func<T, int> function, int value)
    {
        Trace.Assert(function(obj) <= value);
        return obj;
    }
    public static T AssertLessOrEqualsThan<T>(this T obj, Func<T, long> function, long value)
    {
        Trace.Assert(function(obj) <= value);
        return obj;
    }
    public static T AssertGreaterOrEqualsZero<T>(this T obj, Func<T, int> function)
    {
        Trace.Assert(function(obj) >= 0);
        return obj;
    }
    public static T AssertGreaterOrEqualsZero<T>(this T obj, Func<T, long> function, long value)
    {
        Trace.Assert(function(obj) >= 0);
        return obj;
    }
    public static T AssertLessOrEqualsZero<T>(this T obj, Func<T, int> function)
    {
        Trace.Assert(function(obj) <= 0);
        return obj;
    }
    public static T AssertLessOrEqualsZero<T>(this T obj, Func<T, long> function)
    {
        Trace.Assert(function(obj) <= 0);
        return obj;
    }
    public static T RequireTrue<T>(this T obj, Func<T, bool> function)
    {
        return obj.ThrowContractException((o) => function(obj) == true);
    }
    public static T RequireFalse<T>(this T obj, Func<T, bool> function)
    {
        return obj.ThrowContractException((o) => function(obj) == false);
    }
    public static T RequireIsNullOrEmpty<T>(this T obj, Func<T, string> function)
    {
        return obj.ThrowContractException((o) => string.IsNullOrEmpty(function(obj)));
    }
    public static T RequireIsNullOrWhitespace<T>(this T obj, Func<T, string> function)
    {
        return obj.ThrowContractException((o) => string.IsNullOrWhiteSpace(function(obj)));
    }
    public static T RequireIsNotNullOrEmpty<T>(this T obj, Func<T, string> function)
    {
        return obj.ThrowContractException((o) => !string.IsNullOrEmpty(function(obj)));
    }
    public static T RequireIsNotNullOrWhitespace<T>(this T obj, Func<T, string> function)
    {
        return obj.ThrowContractException((o) => !string.IsNullOrWhiteSpace(function(obj)));
    }
    public static T RequireEquals<T, U>(this T obj, Func<T, U> function, U value)
    {
        return obj.ThrowContractException((o) => object.Equals(function(obj), value));
    }
    public static T RequireEquals<T, U>(this T obj, T value)
    {
        return obj.ThrowContractException((o) => object.Equals(obj, value));
    }
    public static T RequireNotEquals<T, U>(this T obj, Func<T, U> function, U value)
    {
        return obj.ThrowContractException((o) => !object.Equals(function(obj), value));
    }
    public static T RequireNotEquals<T, U>(this T obj, T value)
    {
        return obj.ThrowContractException((o) => !object.Equals(obj, value));
    }
    public static T RequireDefault<T, U>(this T obj, Func<T, U> function)
    {
        return obj.ThrowContractException((o) => object.Equals(function(obj), default(U)));
    }
    public static T RequireDefault<T>(this T obj)
    {
        return obj.ThrowContractException((o) => object.Equals(obj, default(T)));
    }
    public static T RequireNonDefault<T, U>(this T obj, Func<T, U> function)
    {
        return obj.ThrowContractException((o) => !object.Equals(function(obj), default(U)));
    }
    public static T RequireNonDefault<T, U>(this T obj)
    {
        return obj.ThrowContractException((o) => !object.Equals(obj, default(T)));
    }
    public static T RequireNotNull<T, U>(this T obj, Func<T, U> function)
    {
        return obj.ThrowContractException((o) => !object.Equals(function(obj), default(U)));
    }
    public static T RequireNotNull<T>(this T obj)
    {
        return obj.ThrowContractException((o) => !object.Equals(obj, default(T)));
    }
    public static T RequireNull<T, U>(this T obj, Func<T, U> function)
    {
        return obj.ThrowContractException((o) => object.Equals(function(obj), default(U)));
    }
    public static T RequireNull<T>(this T obj)
    {
        return obj.ThrowContractException((o) => object.Equals(obj, default(T)));
    }
    public static T RequirePositive<T>(this T obj, Func<T, int> function)
    {
        return obj.ThrowContractException((o) => function(obj) > 0);
    }
    public static T RequirePositive<T>(this T obj, Func<T, long> function)
    {
        return obj.ThrowContractException((o) => function(obj) > 0);
    }
    public static T RequireNegative<T>(this T obj, Func<T, int> function)
    {
        return obj.ThrowContractException((o) => function(obj) < 0);
    }
    public static T RequireNegative<T>(this T obj, Func<T, long> function)
    {
        return obj.ThrowContractException((o) => function(obj) < 0);
    }
    public static T RequireEqualsZero<T>(this T obj, Func<T, int> function)
    {
        return obj.ThrowContractException((o) => function(obj) == 0);
    }
    public static T RequireEqualsZero<T>(this T obj, Func<T, long> function)
    {
        return obj.ThrowContractException((o) => function(obj) == 0);
    }
    public static T RequireNotEqualsZero<T>(this T obj, Func<T, int> function)
    {
        return obj.ThrowContractException((o) => function(obj) != 0);
    }
    public static T RequireNotEqualsZero<T>(this T obj, Func<T, long> function)
    {
        return obj.ThrowContractException((o) => function(obj) != 0);
    }
    public static T RequireGreaterThan<T>(this T obj, Func<T, int> function, int value)
    {
        return obj.ThrowContractException((o) => function(obj) > value);
    }
    public static T RequireGreaterThan<T>(this T obj, Func<T, long> function, long value)
    {
        return obj.ThrowContractException((o) => function(obj) > value);
    }
    public static T RequireLessThan<T>(this T obj, Func<T, int> function, int value)
    {
        return obj.ThrowContractException((o) => function(obj) < value);
    }
    public static T RequireLessThan<T>(this T obj, Func<T, long> function, long value)
    {
        return obj.ThrowContractException((o) => function(obj) < value);
    }
    public static T RequireGreaterOrEqualsThan<T>(this T obj, Func<T, int> function, int value)
    {
        return obj.ThrowContractException((o) => function(obj) >= value);
    }
    public static T RequireGreaterOrEqualsThan<T>(this T obj, Func<T, long> function, long value)
    {
        return obj.ThrowContractException((o) => function(obj) >= value);
    }
    public static T RequireLessOrEqualsThan<T>(this T obj, Func<T, int> function, int value)
    {
        return obj.ThrowContractException((o) => function(obj) <= value);
    }
    public static T RequireLessOrEqualsThan<T>(this T obj, Func<T, long> function, long value)
    {
        return obj.ThrowContractException((o) => function(obj) <= value);
    }
    public static T RequireGreaterOrEqualsZero<T>(this T obj, Func<T, int> function)
    {
        return obj.ThrowContractException((o) => function(obj) >= 0);
    }
    public static T RequireGreaterOrEqualsZero<T>(this T obj, Func<T, long> function, long value)
    {
        return obj.ThrowContractException((o) => function(obj) >= 0);
    }
    public static T RequireLessOrEqualsZero<T>(this T obj, Func<T, int> function)
    {
        return obj.ThrowContractException((o) => function(obj) <= 0);
    }
    public static T RequireLessOrEqualsZero<T>(this T obj, Func<T, long> function)
    {
        return obj.ThrowContractException((o) => function(obj) <= 0);
    }
    public static void ThrowOnFalse<U>(Func<bool> function, params object[] args) where U : Exception, new()
    {
        if (function() == false)
        {
            throw (U)Activator.CreateInstance(typeof(U), args);
        }
    }
    public static void ThrowOnTrue<U>(Func<bool> function, params object[] args) where U : Exception, new()
    {
        if (function() == true)
        {
            throw (U)Activator.CreateInstance(typeof(U), args);
        }
    }
    public static void Throw<U>(Func<bool> function, U ex) where U : Exception
    {
        if (function() == false)
        {
            throw ex;
        }
    }
    public static void AssertTrue(Func<bool> function)
    {
        Trace.Assert(function() == true);
    }
    public static void AssertFalse(Func<bool> function)
    {
        Trace.Assert(function() == false);
    }
    public static void AssertIsNullOrEmpty(Func<string> function)
    {
        Trace.Assert(string.IsNullOrEmpty(function()));
    }
    public static void AssertIsNullOrWhitespace(Func<string> function)
    {
        Trace.Assert(string.IsNullOrWhiteSpace(function()));
    }
    public static void AssertIsNotNullOrEmpty(Func<string> function)
    {
        Trace.Assert(!string.IsNullOrEmpty(function()));
    }
    public static void AssertIsNotNullOrWhitespace(Func<string> function)
    {
        Trace.Assert(!string.IsNullOrWhiteSpace(function()));
    }
    public static void AssertEquals<U>(Func<U> function, U value)
    {
        Trace.Assert(object.Equals(function(), value));
    }
    public static void AssertNotEquals<U>(Func<U> function, U value)
    {
        Trace.Assert(!object.Equals(function(), value));
    }
    public static void AssertDefault<U>(Func<U> function)
    {
        Trace.Assert(object.Equals(function(), default(U)));
    }
    public static void AssertNonDefault<U>(Func<U> function)
    {
        Trace.Assert(!object.Equals(function(), default(U)));
    }
    public static void AssertNotNull<U>(Func<U> function)
    {
        Trace.Assert(!object.Equals(function(), default(U)));
    }
    public static void AssertNull<U>(Func<U> function)
    {
        Trace.Assert(object.Equals(function(), default(U)));
    }
    public static void AssertPositive(Func<int> function)
    {
        Trace.Assert(function() > 0);
    }
    public static void AssertPositive(Func<long> function)
    {
        Trace.Assert(function() > 0);
    }
    public static void AssertNegative(Func<int> function)
    {
        Trace.Assert(function() < 0);
    }
    public static void AssertNegative(Func<long> function)
    {
        Trace.Assert(function() < 0);
    }
    public static void AssertEqualsZero(Func<int> function)
    {
        Trace.Assert(function() == 0);
    }
    public static void AssertEqualsZero(Func<long> function)
    {
        Trace.Assert(function() == 0);
    }
    public static void AssertNotEqualsZero(Func<int> function)
    {
        Trace.Assert(function() != 0);
    }
    public static void AssertNotEqualsZero(Func<long> function)
    {
        Trace.Assert(function() != 0);
    }
    public static void AssertGreaterThan(Func<int> function, int value)
    {
        Trace.Assert(function() > value);
    }
    public static void AssertGreaterThan(Func<long> function, long value)
    {
        Trace.Assert(function() > value);
    }
    public static void AssertLessThan(Func<int> function, int value)
    {
        Trace.Assert(function() < value);
    }
    public static void AssertLessThan(Func<long> function, long value)
    {
        Trace.Assert(function() < value);
    }
    public static void AssertGreaterOrEqualsThan(Func<int> function, int value)
    {
        Trace.Assert(function() >= value);
    }
    public static void AssertGreaterOrEqualsThan(Func<long> function, long value)
    {
        Trace.Assert(function() >= value);
    }
    public static void AssertLessOrEqualsThan(Func<int> function, int value)
    {
        Trace.Assert(function() <= value);
    }
    public static void AssertLessOrEqualsThan(Func<long> function, long value)
    {
        Trace.Assert(function() <= value);
    }
    public static void AssertGreaterOrEqualsZero(Func<int> function)
    {
        Trace.Assert(function() >= 0);
    }
    public static void AssertGreaterOrEqualsZero(Func<long> function, long value)
    {
        Trace.Assert(function() >= 0);
    }
    public static void AssertLessOrEqualsZero<T>(Func<int> function)
    {
        Trace.Assert(function() <= 0);
    }
    public static void AssertLessOrEqualsZero(Func<long> function)
    {
        Trace.Assert(function() <= 0);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.