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?
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.