Search code examples
c#factorybuilderstrong-typing

Implement a non-generic, static Factory method to create various Generic Classes from string input (without using "dynamic" type)


I have a set of classes which are defined and populated from parsed XML.

As part of this, I want to be able to dynamically instantiate collection classes for particular types, as specified by the XML (in this case, to manage / instantiate exceptions).

So I have a class, approximately defined as follows:

public class ExceptionGroup<T> : BaseCollectionClass<Exception> where T : Exception 
{
    // contents of this class don't really matter
}

When I process the XML, I will have the name of the Exception-derived type contained in a string (i.e. "ArgumentNullException", "InvalidDataException", "IndexOutOfRangeException" etc.), as well as the content (message) that will be used to populate the generated Exception (also a string) when/if it's thrown.

So, to get where I'm trying to go, I first have implemented a couple of relevant static classes (defined elsewhere):

// Recursively determines if a supplied Type is ultimately derived from the Exception class:
public static bool IsException( Type type ) =>
    (type == typeof( object )) ? false : (type == typeof(Exception)) || IsException( type.BaseType );

// Figures out if the specified string contains the name of a recognized Type 
// and that the Type itself is genuinely derived from the Exception base class:
public static Type DeriveType( string name )
{
    // If the string is null, empty, whitespace, or malformed, it's not valid and we ignore it...
    if ( !string.IsNullOrWhiteSpace( name ) && Regex.IsMatch( name, @"^([a-z][\w]*[a-z0-9])$", RegexOptions.IgnoreCase ) )
        try
        {
            Type excType = System.Type.GetType( name );
            if ( IsException( excType ) ) return excType;
        }
        catch { }

    // The type could not be determined, return null:
    return null; 
}

Using these classes, I can take an input string and end up with a known, existing, C# Type class (presuming the input string is valid) that's derived from the Exception class. Now I want to build a factory method that can create new ExceptionGroup<T> objects where "T" is the object type that's derived from the orignal string.

I've kind of managed this by using the dynamic type as the factory's return type as follows:

public static dynamic CreateExceptionGroup( string exceptionTypeName )
{
    Type excType = DeriveType( exceptionTypeName );
    if ( !(excType is null) )
    {
        Type groupType = typeof( ExceptionGroup<> ).MakeGenericType( new Type[] { excType } );
        return Activator.CreateInstance( groupType );
    }
    return null;
}

I'm quite uncomfortable with this though, both because I dislike the ambiguity / uncertainty of the result, and because subsequently working with that result can be more cumbersome/complex. I'd much rather actually specify the return type more concretely, and then appropriately cast/qualify it in some manner, for example (yes, I know that this isn't valid!):

public static ExceptionGroup<> CreateGroup( string exceptionTypeName )
{
    Type excType = DeriveType( exceptionTypeName );
    if ( !(excType is null) )
    {
        Type[] types = new Type[] { excType };
        Type groupType = typeof( ExceptionGroup<> ).MakeGenericType( types );
        return (ExceptionGroup<>)Activator.CreateInstance( groupType );
    }
    return null;
}

...but, of course ExceptionGroup<> isn't valid in this syntax/context. (Generates "CS7003: Unexpected use of an unbound generic name"), and neither is merely using ExceptionGroup (Generates: "CS0305: Using the generic type 'ExceptionGroup' requires 1 type arguments.")

So, IS there a way to do this with strong(er) typing, via some other syntax or mechanism, with more precisely described results, or is using dynamic, with all of the subsequent associated overhead, literally the only way to accomplish this?


Solution

  • While I hope that there may be a more simple/ succinct solution (and would love to see it if so!) some retrospective prompting by Sweeper led me to the realisation that I could essentially bypass the problem by injecting the overhead of a new abstract ancestor class:

    public abstract class ExceptionGroupFoundation : BaseCollectionClass<Exception>
    {
        public ExceptionGroupFoundation( object[] args = null ) : base( args ) { }
    
        // Implement necessary common accessors, methods, fields, properties etc here...
        // (preferably "abstract" as/when/where possible)
    }
    

    ... then deriving my generic class from that one:

    public class ExceptionGroup<T> : ExceptionGroupFoundation where T : Exception 
    {
        public ExceptionGroup( object[] args = null ) : base( args ) { }
    
        // contents of this class don't really matter
    }
    

    ... then declaring my factory method using the new abstract class as the return type:

    public static ExceptionGroupFoundation CreateGroup( string exceptionTypeName )
    {
        Type excType = DeriveType( exceptionTypeName );
        if ( !(excType is null) )
        {
            Type[] types = new Type[] { excType };
            Type groupType = typeof( ExceptionGroup<> ).MakeGenericType( types );
            return (ExceptionGroupFoundation)Activator.CreateInstance( groupType );
        }
        return null;
    }
    

    ...to essentially arrive at the desired result, albeit somewhat cumbersomely/awkwardly.