Graham
Graham

Reputation: 1517

How to Write Generic Extension Method to Convert Type in C#

I am writing a static guard class/api to validate parameters sent to methods.

Code so far looks like:

public static class Guard
{
    public static GuardArgument<T> Ensure<T>(T value, string argumentName)
    {
        return new GuardArgument<T>(value, argumentName);
    }

    public static T Value<T>(this GuardArgument<T> guardArgument)
    {
        return guardArgument.Value;
    }

    // Example extension method
    public static GuardArgument<T> IsNotNull<T>(this GuardArgument<T> guardArgument, string errorMessage)
    {
        if (guardArgument.Value == null)
        {
            throw new ArgumentNullException(guardArgument.Name, errorMessage);
        }

        return guardArgument;
    }
}

It can be used as so:

public void Test(IFoo foo) {

     Guard.Ensure(foo, "foo").IsNotNull();
}

Circumstances now require that I need to cast to concrete types from a supplied interface. Don't ask why, I just need to!

I want to add an As extension method to GuardArgument to do this, something like:

public static GuardArgument<TOut> As<TOut, TIn>(this GuardArgument<TIn> guardArgument, Type type)
        where TOut : class
    {
        // Check cast is OK, otherwise throw exception

        return new GuardArgument<TOut>(guardArgument.Value as TOut, guardArgument.Name);
    }

I don't much like the syntax though. I want to be able to use the class as follows:

 Foo foo = Guard.Ensure(foo, "foo")
             .As(typeof(Foo))
             .IsNotNull()
             .Value();

I'm not sure how to write the extension method to allow this syntax though. I realise I can use the existing fluent API as:

 Foo foo = Guard.Ensure(foo as Foo, "foo")
             .IsNotNull()
             .Value();

but I don't like this from a readability perspective.

Upvotes: 3

Views: 1245

Answers (2)

Anders Abel
Anders Abel

Reputation: 69250

You can get this syntax:

Foo foo = Guard.Ensure(foo, "foo")
          .As<Foo>()
          .IsNotNull()
          .Value();

The trick is to ditch the TIn type param. It's not used in the As() method and bloats the API when type inference can't be used due to TOut. To be able to do that without getting As() suggested on all types you have to implement a new, non-generic interface for your GuardArgument<> class:

interface IGuardArgument 
{ 
  object Value { get; }
  strign Name { get; }
}

public class GuardArgument<T> : IGuardArgument
{
  // Explicit implementation to hide this property from
  // intellisense.
  object IGuardArgument.Value { get { return Value; } 

  // Rest of class here, including public properties Value and Name.
}

Now you can write the As() method with only one generic param:

public static GuardArgument<TOut> As<TOut>(this IGuardArgument guardArgument)
    where TOut : class
{
    // Check cast is OK, otherwise throw exception

    return new GuardArgument<TOut>(guardArgument.Value as TOut, guardArgument.Name);
}

Upvotes: 5

Andy Lamb
Andy Lamb

Reputation: 2221

Introduce an IGuardArgument interface which GuardArgument{T} implements. Then you can remove TIn from the As extension method and remove the Type parameter. Signature:

public static GuardArgument<TOut> As(this IGuardArgument guardArgument);

Usage:

Guard.Ensure(foo, "foo").As<Foo>().IsNotNull()

Upvotes: 2

Related Questions