#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
возвращает, и проверяет последующие выражения для вас? В лучшем случае вы получаете интерфейс anExceptionGroup<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;
}
…по существу, достичь желаемого результата, хотя и несколько громоздко / неуклюже.