英文:
How to check assembly exists before calling a class method in C#
问题
我正在开发一个应用程序,其中包含两个支持的DLL项目。ClassLibrary1.dll
和 ClassLibrary2.dll
。ClassLibrary2.dll 是可选的,只被ClassLibrary1.dll
使用。该应用程序具有显式导入DLL的功能。如果我将这两个DLL都导入应用程序,一切都正常。
问题出现在我不导入可选的ClassLibrary2.dll时。
在应用程序启动时,我会检查程序集是否存在:
var assemblyName = "ClassLibrary2.dll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var assembly = (from a in assemblies
where a.FullName == assemblyName
select a).SingleOrDefault();
// 当DLL存在时,IsClassLibrary2Exists属性变为true
if (assembly != null)
{
Props.IsClassLibrary2Exists = true;
}
以下是我在ClassLibrary1中调用ClassLibrary2方法的方式:
if(Props.IsClassLibrary2Exists){
ClassLibrary2.SpecialProduct.GetSpecialProduct(Id);
}
当程序集不存在时,我会收到错误消息:
"System.IO.FileNotFoundException: '无法加载文件或程序集 'ClassLibrary2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' 或其某个依赖项。系统找不到指定的文件。'"
英文:
I am working on an application with two supporting DLLs in the Project. ClassLibrary1.dll
and ClassLibrary2.dll
. The ClassLibrary2.dll is optional and only used by the ClassLibrary1.dll
. The application has a feature to import the DLL explicitly. Everything worked fine if I import both DLLs into the application.
The problem arises when I don't import the optional ClassLibrary2.dll.
Here on application start, I am checking whether assembly exists or not:
var assemblyName = "ClassLibrary2.dll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var assembly = (from a in assemblies
where a.FullName == assemblyName
select a).SingleOrDefault();
// this IsClassLibrary2Exists property become true when the DLL exists
if (assembly != null)
{
Props.IsClassLibrary2Exists = true;
}
Here is How I am calling the ClassLibrary2 method in ClassLibrary1
if(Props.IsClassLibrary2Exists){
ClassLibrary2.SpecialProduct.GetSpecialProduct(Id);
}
I am getting an error when the assembly does not exist:
> "System.IO.FileNotFoundException: 'Could not load file or assembly 'ClassLibrary2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.'"
答案1
得分: 1
我同意Dai的评论,根本问题在于ClassLibrary1
对ClassLibrary2
有硬编码的依赖,所以关键问题是如何解耦它们。
其中一种解决方法是创建一个Interface
类库,其中只包含应用程序可能尝试使用的插件接口(但同时可能没有附加实例)。这个类库将被插件服务器和客户端引用。
Interface
类库
namespace Interface
{
/// <summary>
/// 作为此应用程序的插件至少需要实现IPlugin接口。
/// </summary>
public interface IPlugin { }
public interface ISpecialProduct : IPlugin
{
string GetSpecialProduct(string id);
}
}
ClassLibrary1
SpecialProduct
成员与ClassLibrary2
解耦,因为它是一个接口而不是一个类。
using Interface; // 但不是ClassLibrary2
namespace ClassLibrary1
{
public class Product
{
public ISpecialProduct SpecialProduct { get; set; }
}
}
ClassLibrary2
using Interface; // 唯一的依赖
namespace ClassLibrary2
{
public class SpecialProduct : ISpecialProduct
{
public string GetSpecialProduct(string id) => Guid.NewGuid().ToString();
}
}
测试(控制台应用程序的概念验证)
可用插件位于应用程序运行目录的Plugins
子文件夹中。最初不引用或加载ClassLibrary2
。
using Interface;
using ClassLibrary1; // 但不是ClassLibrary2
internal class Program
{
static void Main(string[] args)
{
Console.Title = "测试可选库";
Assembly assySpecial;
Product product = new Product(); // 在ClassLibrary1中
#region 未加载
// 尝试获取程序集(不要求强命名或特定版本)
assySpecial =
AppDomain
.CurrentDomain
.GetAssemblies()
.FirstOrDefault(_ => _.GetName().Name.Equals("ClassLibrary2"));
Debug.Assert(assySpecial == null, "期望未加载程序集");
Debug.Assert(product.SpecialProduct == null, "期望SpecialProduct为null");
if (product.SpecialProduct == null)
{
Console.WriteLine("SpecialProduct尚未加载。");
}
#endregion 未加载
#region 加载
var pluginPath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"Plugins",
"ClassLibrary2.dll");
if (File.Exists(pluginPath))
{
Assembly.LoadFrom(pluginPath);
}
#endregion 加载
#region 已加载
assySpecial =
AppDomain
.CurrentDomain
.GetAssemblies()
.FirstOrDefault(_ => _.GetName().Name.Equals("ClassLibrary2"));
Debug.Assert(assySpecial != null, "期望已加载程序集");
if (assySpecial != null)
{
product.SpecialProduct =
(ISpecialProduct)
Activator.CreateInstance(
assySpecial
.GetTypes()
.First(_ => _.Name.Equals("SpecialProduct")));
}
Console.WriteLine($"SpecialProduct: {product.SpecialProduct?.GetSpecialProduct("123")}");
Console.ReadKey();
#endregion 已加载
}
}
上述代码中的内容已翻译完成,没有其他内容。
英文:
I agree with the comment by Dai that the underlying issue is that ClassLibrary1
has a hardcoded dependency on ClassLibrary2
so the matter falls to how this can be decoupled.
One approach that has worked for me is to have an Interface
class library consisting only of plugin interfaces that the application may attempt to use (but which, at the same time, might not have instances attached). It will be referenced by plugin servers and clients alike.
Interface
class library
namespace Interface
{
/// <summary>
/// To be a plugin for this app requires IPlugin at minimum .
/// </summary>
public interface IPlugin { }
public interface ISpecialProduct : IPlugin
{
string GetSpecialProduct(string id);
}
}
ClassLibrary1
The SpecialProduct
member is decoupled from ClassLibrary2
because it's an interface not a class.
using Interface; // But 'not' ClassLibrary2
namespace ClassLibrary1
{
public class Product
{
public ISpecialProduct SpecialProduct { get; set; }
}
}
ClassLibrary2
using Interface; // The only dependency
namespace ClassLibrary2
{
public class SpecialProduct : ISpecialProduct
{
public string GetSpecialProduct(string id) => Guid.NewGuid().ToString();
}
}
Test (proof of concept using console app)
Available plugins are located in the Plugins
subfolder of the application's run directory. ClassLibrary2
is not initially referenced or loaded.
using Interface;
using ClassLibrary1; // But 'not' ClassLibrary2
internal class Program
{
static void Main(string[] args)
{
Console.Title = "Test optional lib";
Assembly assySpecial;
Product product = new Product(); // In ClassLibrary1
#region N O T L O A D E D
// Try get assy (not strong named or picky about version)
assySpecial =
AppDomain
.CurrentDomain
.GetAssemblies()
.FirstOrDefault(_ => _.GetName().Name.Equals("ClassLibrary2"));
Debug.Assert(assySpecial == null, "Expecting assy not loaded");
Debug.Assert(product.SpecialProduct == null, "Expecting null SpecialProduct");
if(product.SpecialProduct == null)
{
Console.WriteLine("SpecialProduct is not loaded yet.");
}
#endregion N O T L O A D E D
#region L O A D
var pluginPath = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"Plugins",
"ClassLibrary2.dll");
if(File.Exists(pluginPath))
{
Assembly.LoadFrom(pluginPath);
}
#endregion L O A D
#region L O A D E D
assySpecial =
AppDomain
.CurrentDomain
.GetAssemblies()
.FirstOrDefault(_ => _.GetName().Name.Equals("ClassLibrary2"));
Debug.Assert(assySpecial != null, "Expecting assy loaded");
if (assySpecial != null)
{
product.SpecialProduct =
(ISpecialProduct)
Activator.CreateInstance(
assySpecial
.GetTypes()
.First(_ => _.Name.Equals("SpecialProduct")));
}
Console.WriteLine($"SpecialProduct: {product.SpecialProduct?.GetSpecialProduct("123")}");
Console.ReadKey();
#endregion L O A D E D
}
}
答案2
得分: -1
为了成功地防止一个程序集需要另一个程序集,您需要通过不执行的方法来完成对"程序集2"的所有访问。仅仅使用if
来保护访问是不够的,因为整个方法需要首先进行JIT编译,这需要加载另一个程序集。
可能会起作用的方法:
...
if (Props.IsClassLibrary2Exists)
DoThingsWithLibrary2();
...
void DoThingsWithLibrary2()
{
// 对Library2的访问必须限定在不执行的方法内
ClassLibrary2.SpecialProduct.GetSpecialProduct(Id);
}
请注意,很容易以难以修复的方式在运行时使用"另一个库" - 静态属性/构造函数可以随时调用,因此在那里引用来自另一个库的任何内容都会失败,即使通过反射使用字段/属性/方法参数/返回来自另一个库的类型也会失败...就像许多其他情况一样。
如果可能的话,最好动态加载程序集并让它实现共享接口,这样您可以将反射限制在实例化部分,但通过强类型接口使用这些类。
英文:
To have any success in preventing one assembly to require the other one you need all access to the "assembly 2" to be done via methods that are not executed. Protecting access with if
is not enough because whole method need to be JITed first and that requires loading the other assembly.
What likely would work:
...
if (Props.IsClassLibrary2Exists)
DoThingsWithLibrary2();
...
void DoThingsWithLibrary2()
{
// access to Library2 feature must be scoped to methods
// that are not executed
ClassLibrary2.SpecialProduct.GetSpecialProduct(Id);
}
Note that it is extremely easy to use "the other library" in a way that is hard/impossible to fix at run-time - static properties/constructors can be called at any time and hence referencing anything from the other library there will fail, having fields/properties/method parameters/return to use types from the other library would fail even via reflection... and like many other cases.
If possible prefer to load the assembly dynamically and have it implement shared interfaces so you can limit reflection to instantiation part, but use those classes via strongly typed interfaces.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论