简体   繁体   中英

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 (ie "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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM