Обязательное чтение:
Прочтите эту статью MSDN: Рекомендации по загрузке сборки
Короче говоря:
Похоже, вы предполагаете, что класс System.Data.SqlClient.SqlConnection
всегда существует внутри System.Data.SqlClient.dll
.
Это неверное предположение:
- Пакет NuGet не является сборкой .NET.
- Пакет NuGet не сопоставляет 1: 1 со сборкой .NET и пространствами имен.
- Пакет NuGet может содержать несколько сборок.
- Пакет NuGet может содержать ноль сборок.
- Пакет NuGet может содержать сборки, в которых вообще не определены типы!
- Это могут быть сборки, содержащие только Ресурсы или другие встроенные элементы
- Это могут быть сборки, использующие перенаправление типов для перенаправления типов, ранее существовавших в этой сборке, на другие сборки. Только JIT использует эту функцию, однако, не отражение.
- И эти «перенаправленные» сборки не обязательно должны существовать в пакетах NuGet: они могут быть «встроенными» сборками, встроенными в среду выполнения, например
mscorlib.dll
и System.Data.dll
).
- Они могут быть сборками-заглушками, которые не предоставляют никаких типов, если эти типы уже предоставлены библиотекой базовых классов - пакет NuGet существует только для предоставления этих типов для других платформ.
- Это ситуация, с которой вы имеете дело.
- Пакет NuGet может иметь очень разные эффекты в зависимости от цели проекта (.NET Framework, .NET Standard, .NET Core и т. Д.)
Ваш код не может предполагать, что определенный класс находится в определенном файле сборки - это нарушает представление .NET о обратной совместимости посредством пересылки типов.
В вашем случае ...
В вашем случае ваш код предполагает, что System.Data.SqlClient.SqlConnection
существует внутри файла сборки с именем System.Data.SqlClient
. Это предположение ложно во многих случаях, но верно в некоторых случаях.
Вот структура каталогов верхнего уровня пакета System.Data.SqlClient
NuGet:
Обратите внимание, как внутри пакета есть подкаталоги для каждой поддерживаемой цели (в данном случае MonoAndroid10, MonoTouch10, net46, net451, net461, netcoreapp2.1, netstandard1.2 и т. Д.). Для каждой из этих целей пакет предоставляет различных сборок :
При нацеливании на .NET Framework 4.5.1, .NET Framework 4.6 или .NET Framework 4.6.1 будут использоваться файлы из каталогов net451
, net46
и net461
(соответственно). Эти папки содержат один файл с именем System.Data.SqlClient.dll
, который не содержит классов . Это связано с тем, что при нацеливании на .NET Framework 4.x типы System.Data.SqlClient
(пространство имен) уже предоставляются библиотекой базовых классов внутри System.Data.dll
, поэтому никаких дополнительных типов не требуется. (Так что, если вы создаете только для .NET Framework 4.x, вам не нужен пакет NuGet System.Data.SqlClient
.
Вот скриншот внутренней части этого файла сборки с помощью инструмента .NET Reflector (инструмента, который позволяет вам просматривать и декомпилировать сборки .NET), если вы мне не верите:
При нацеливании на другие платформы через .NET Standard (то есть, когда System.Data.dll
не включен по умолчанию или когда System.Data.dll
не включает SqlClient
), тогда пакет NuGet будет использовать netstandard1.2
, Каталоги netstandard1.3
, netstandard2.0
, которые содержат , содержат System.Data.SqlClient.dll
, а содержит - пространство имен System.Data.SqlClient
с типами, которые вы ищете. Вот скриншот этой сборки:
И другие платформы, такие как MonoAndroid
, MonoTouch
, xamarinios
, xamarintvos
и т. Д., Также имеют свою собственную версию файла сборки (или файлов!).
Но даже если вы знаете, что ваша программа будет работать только на одной конкретной платформе, где конкретный пакет NuGet содержит DLL-сборку, содержащую определенный тип, - это все равно «неправильно» из-за переадресации: https://docs.microsoft.com/en-us/dotnet/framework/app-domains/type-forwarding-in-the-common-language-runtime
Хотя переадресация типов означает, что большинство программ, которые ссылаются на типы в определенных сборках, будут продолжать работать нормально, это не относится к загрузке сборок и загрузке типов на основе отражений, что и делает ваш код. Рассмотрим этот сценарий:
- Выпущена новая версия
System.Data.SqlClient
пакета NuGet, который теперь имеет две сборки:
System.Data.SqlClient.dll
(то же, что и раньше, за исключением того, что SqlConnection
удалено, но для него установлен атрибут [TypeForwardedTo]
, который цитирует System.Data.SqlClient.SqlConnection.dll
).
System.Data.SqlClient.SqlConnection.dll
(класс SqlConnection
теперь живет в этой сборке).
- Ваш код теперь будет ломаться, потому что он явно загружает только
System.Data.SqlClient.dll
, а не System.Data.SqlClient.SqlConnection.dll
и перечисляет эти типы.
Здесь будут драконы ...
Теперь, если вы готовы проигнорировать все эти рекомендации и написать программы, которые предполагают, что в конкретной сборке существует определенный тип, тогда процесс прост:
// Persistent state:
Dictionary<String,Assembly> loadedAssemblies = new Dictionary<String,Assembly>();
Dictionary<(String assembly, String typeName),Type> typesByAssemblyAndName = new Dictionary<(String assembly, String typeName),Type>();
// The function:
static Type GetExpectedTypeFromAssemblyFile( String assemblyFileName, String typeName )
{
var t = ( assemblyFileName, typeName );
if( !typesByName.TryGetValue( t, out Type type ) )
{
if( !loadedAssemblies.TryGetValue( assemblyFileName, out Assembly assembly ) )
{
assembly = Assembly.LoadFrom( assemblyFileName );
loadedAssemblies[ assemblyFileName ] = assembly;
}
type = assembly.GetType( typeName ); // throws if the type doesn't exist
typesByName[ t ] = type;
}
return type;
}
// Usage:
static IDbConnection CreateSqlConnection()
{
const String typeName = "System.Data.SqlClient.SqlConnection";
const String assemblyFileName = "System.Data.SqlClient.dll";
Type sqlConnectionType = GetExpectedTypeFromAssemblyFile( assemblyFileName, typeName );
Object sqlConnectionInstance = Activator.CreateInstance( sqlConnectionType ); // Creates an instance of the specified type using that type's default constructor.
return (IDbConnection)sqlConnectionInstance;
}