Реализовать нестандартный статический фабричный метод для создания различных универсальных классов из строкового ввода (без использования «динамического» типа)

#c# #factory #builder #strong-typing

#c# #фабрика #Конструктор #строгая типизация

Вопрос:

У меня есть набор классов, которые определены и заполнены из проанализированного XML.

В рамках этого я хочу иметь возможность динамически создавать экземпляры классов коллекции для определенных типов, как указано в XML (в данном случае для управления / создания исключений).

Итак, у меня есть класс, приблизительно определенный следующим образом:

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

Когда я обрабатываю XML, у меня будет имя производного от исключения типа, содержащегося в строке (т.Е. «ArgumentNullException», «InvalidDataException», «IndexOutOfRangeException» и т. Д.), А также содержимое (сообщение), которое будет использоваться для заполнения сгенерированного исключения (также строка)когда / если он выдается.

Итак, чтобы добраться туда, куда я пытаюсь, я сначала реализовал пару соответствующих статических классов (определенных в другом месте):

 // 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 ) amp;amp; 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; 
}
 

Используя эти классы, я могу взять входную строку и получить известный существующий класс типа C # (предполагая, что входная строка допустима), который является производным от класса Exception . Теперь я хочу создать фабричный метод, который может создавать новые ExceptionGroup<T> объекты, где «T» — это тип объекта, полученный из исходной строки.

Я вроде бы справился с этим, используя dynamic тип в качестве возвращаемого типа фабрики следующим образом:

 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;
}
 

Однако мне это очень не нравится, как потому, что мне не нравится двусмысленность / неопределенность результата, так и потому, что впоследствии работа с этим результатом может быть более громоздкой / сложной. Я бы предпочел на самом деле указать возвращаемый тип более конкретно, а затем соответствующим образом использовать / квалифицировать его каким-либо образом, например (да, я знаю, что это недопустимо!):

 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;
}
 

…но, конечно ExceptionGroup<> , недопустимо в этом синтаксисе / контексте. (Генерирует «CS7003: неожиданное использование несвязанного универсального имени»), и ни один из них не является простым использованием ExceptionGroup (генерирует: «CS0305: использование универсального типа «ExceptionGroup» требует аргументов типа 1″.)

Итак, есть ли способ сделать это с помощью строгой (er) типизации, с помощью какого-либо другого синтаксиса или механизма, с более точно описанными результатами, или использование dynamic , со всеми последующими связанными накладными расходами, буквально единственный способ добиться этого?

Комментарии:

1. Почему не может CreateGroup быть универсальным? Вы не знаете, какую группу исключений вы получаете при вызове CreateGroup ? Если вы знаете, вы можете указать тип исключения, который вы хотите, в качестве универсального параметра. Если вы не знаете, есть два случая. Либо это не может быть безопасным для типов, либо может быть, но вы всегда получите ExceptionGroup<Exception> (поскольку вы не знаете ничего больше, чем это во время компиляции). В каком случае это зависит от содержимого ExceptionGroup .

2. Как показывает пример кода, при CreateGroup вызове все, что у меня есть, это строка неизвестного содержимого. У меня даже нет класса заполненного ТИПА, не говоря уже о том, чтобы каким-то образом указать универсальный в вызывающем коде… По какой-то причине C # способен понять эту потребность / формулировку в Type groupType = typeof( ExceptionGroup<> ) контексте, но не в контексте возвращаемого типа функции.

3.Тогда скажите мне, каким образом компилятор узнает, какой конкретный тип ExceptionGroup CreateGroup возвращает, и проверяет последующие выражения для вас? В лучшем случае вы получаете интерфейс an ExceptionGroup<Exception> во время компиляции, предполагая, что члены ExceptionGroup могут быть преобразованы в ковариантный интерфейс.

4. Предположительно, теми же способами, которые ему удается выполнить в действительном Type groupType = typeof( ExceptionGroup<> ) объявлении, и / или тем же способом (способами), которым ему удается делать правильные выводы в where объявлениях универсального типа (т. Е. ... where T : Class Когда компилятор четко может определить, что «класс» (в данном контексте) включает в себя не толькоСам «класс», но и все типы, производные от класса).

5. Хорошо, я понимаю, что вы говорите, но компилятор не делает этого автоматически. Для этого вам нужно объявить ковариантный интерфейс. Можете ли вы объявить ковариантный интерфейс для всех членов ExceptionGroup , зависит от его содержимого. Пожалуйста, покажите содержимое ExceptionGroup . Достаточно просто подписей, нет необходимости в реализациях.

Ответ №1:

Хотя я надеюсь, что может быть более простое / краткое решение (и хотел бы его увидеть, если это так!), Некоторые ретроспективные подсказки Sweeper привели меня к осознанию того, что я мог бы существенно обойти проблему, введя накладные расходы нового абстрактного класса-предка:

 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)
}
 

… затем извлекаю мой универсальный класс из этого:

 public class ExceptionGroup<T> : ExceptionGroupFoundation where T : Exception 
{
    public ExceptionGroup( object[] args = null ) : base( args ) { }

    // contents of this class don't really matter
}
 

… затем объявляю свой фабричный метод, используя новый абстрактный класс в качестве возвращаемого типа:

 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;
}
 

…по существу, достичь желаемого результата, хотя и несколько громоздко / неуклюже.