如何防止程序集在未被使用时加载

3

我正在使用Winforms,C# .NET4.5

我有一个在A程序集中的类叫做classA,它提供了method1方法。

在我的主程序集中,我可以调用这个方法,但在某些情况下,我可以100%确定这是不可能发生的,因为来自注册表键的某些内部标志:

if( valueFromRegistryIsAlwaysFalse)
{
   var A = new classA();
   A.method1();
}

当我在注册表中设置这个值时,我不想提供程序 A 的主可执行文件,然而即使我没有使用该方法,它仍需要程序 A 才能启动。当然我理解这一点,但是否有办法规避这个问题呢?


只是为了澄清一下:您有一个可能存在或不存在的程序集,以及一个应该指示是否需要使用其中的类的注册表设置,但如果不需要其中的类,则希望允许应用程序在不尝试加载该程序集的情况下运行? - Andrew Barber
1
奇怪...IME JIT在代码需要从程序集中获取内容之前不会加载程序集。你说这种情况没有发生。你是否测试过以验证即使没有使用代码,程序集也会被加载?你还验证了在此之前没有其他代码需要程序集A吗? - P.Brian.Mackey
@P.Brian.Mackey,它需要汇编来JIT方法;避免这种情况通常是一个非常简单的重构。 - Marc Gravell
我们的应用程序有两个版本:一个控制硬件,另一个则不涉及硬件。控制所有硬件的程序集在整个主应用程序中都被使用,重构这个程序集是一项巨大的工作(我将不被允许这样做)... 我已经测试过了。 - Enrico
4个回答

10

通常,在JIT编译包含使用尚未加载的程序集的类型的方法时,程序集加载和融合会按需发生。所以:“修复”这个问题的方法是将涉及此程序集中的类型的所有代码移动到永远不会执行的方法中。例如:

if( valueFromRegistryIsAlwaysFalse)
{
   DoStuff();
}

[System.Runtime.CompilerServices.MethodImpl(
    System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
void DoStuff()
{
   var A = new classA();
   A.method1();
}

通过这种改变,原始方法不再提及 classA,因此该方法可以进行即时编译而无需额外的汇编;并且由于 DoStuff 从未被执行/ JIT编译,因此它也不会从那里加载。


谢谢,这正是我在寻找的!这是文档化的行为吗?它将适用于未来的.NET框架吗?或者现在我要求太多了吗? - Enrico
@Enrico,对于未来的.NET框架中什么有效或无效,我无法发表评论 - 我在上次搬家时丢失了我的水晶球。 - Marc Gravell
我知道我要求太多了,但我可以试一试!谢谢! - Enrico

1

通常框架按需加载程序集。另一种方法是使用动态加载模式或组合模式。从框架4.0开始,微软引入了System.Addin命名空间来帮助程序员实现此目的。我在自己的项目中使用以下类来实现与Framework 2.0兼容性:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;

namespace MyNamespace
{
    [Serializable]
    public enum CompositeLoaderFilter
    {
        ImplementsInterface = 0,
        InheritsBaseClass = 1
    }

    [Serializable]
    public static class Composite
    {
        private static readonly CompositeManager manager = new CompositeManager();

        public static CompositeManager Manager { get { return manager; } }
    }

    [Serializable]
    public class CompositeManager : MarshalByRefObject
    {
        private SortedList<string, Type> m_addIns;

        public int GetInMemoryComponents(Type addInType, CompositeLoaderFilter filter)
        {
            m_addIns = internal_GetInMemoryServices(addInType, filter);
            return m_addIns.Count;
        }

        public int GetComponents(Type addInType, CompositeLoaderFilter filter)
        {
            string addInPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            return GetComponents(addInPath, "*.dll", SearchOption.TopDirectoryOnly, addInType, filter);
        }

        public int GetComponents(string addInSearchPattern, Type addInType, CompositeLoaderFilter filter)
        {
            string addInPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            return GetComponents(addInPath, addInSearchPattern, SearchOption.TopDirectoryOnly, addInType, filter);
        }

        public int GetComponents(string addInPath, string addInSearchPattern, Type addInType, CompositeLoaderFilter filter)
        {
            return GetComponents(addInPath, addInSearchPattern, SearchOption.TopDirectoryOnly, addInType, filter);
        }

        public int GetComponents(string addInPath, string addInSearchPattern, SearchOption addInSearchOption, Type addInType, CompositeLoaderFilter filter)
        {
            AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
            setup.PrivateBinPath = addInPath;
            setup.ShadowCopyFiles = "false";

            AppDomain m_appDomain = AppDomain.CreateDomain("MyNamespace.CompositeManager", null, setup);
            CompositeManager m_remoteLoader = (CompositeManager)m_appDomain.CreateInstanceFromAndUnwrap(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MyAssembly.dll"), "MyNamespace.CompositeManager");
            m_addIns = m_remoteLoader.RemoteGetServices(addInPath, addInSearchPattern, addInSearchOption, addInType, filter);

    #if DEBUG
            DebugLoadedAssemblies();
    #endif

            AppDomain.Unload(m_appDomain);
            return m_addIns.Count;
        }

        public object CreateInstance(string typeName)
        {
            if (!m_addIns.ContainsKey(typeName))
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Type {0} was not loaded..", typeName), "typeName");

            MethodInfo method = m_addIns[typeName].GetMethod("GetInstance", BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            if (method != null)
                return method.Invoke(m_addIns[typeName], null);
            else return Activator.CreateInstance(m_addIns[typeName]);
        }

        public object CreateInstance(Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type", "Type is null");

            if (!m_addIns.ContainsKey(type.FullName))
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Type {0} was not loaded..", type.FullName), "type");

            MethodInfo method = type.GetMethod("GetInstance", BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            if (method != null)
                return method.Invoke(type, null);
            else return Activator.CreateInstance(type);
        }

        public T CreateInstance<T>()
        {
            if (!m_addIns.ContainsKey(typeof(T).FullName))
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Type {0} was not loaded..", typeof(T).FullName), "T");

            MethodInfo method = typeof(T).GetMethod("GetInstance", BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            if (method != null)
                return (T)method.Invoke(typeof(T), null);
            else return Activator.CreateInstance<T>();
        }

        public IEnumerable<Type> AvailableServices
        {
            get
            {
                foreach (KeyValuePair<string, Type> item in m_addIns)
                {
                    yield return item.Value;
                }
            }
        }

        public Type[] AvailableTypes
        {
            get
            {
                List<Type> list = new List<Type>();
                foreach (KeyValuePair<string, Type> item in m_addIns)
                    list.Add(item.Value);
                return list.ToArray();
            }
        }

        public T[] GetObjects<T>()
        {
            List<T> list = new List<T>();
            foreach (KeyValuePair<string, Type> item in m_addIns)
                list.Add((T)CreateInstance(item.Value));
            return list.ToArray();
        }

        public object[] AvailableObjects
        {
            get
            {
                List<object> list = new List<object>();
                foreach (KeyValuePair<string, Type> item in m_addIns)
                    list.Add(CreateInstance(item.Value));
                return list.ToArray();
            }
        }

        internal SortedList<string, Type> internal_GetInMemoryServices(Type addInType, CompositeLoaderFilter filter)
        {
            SortedList<string, Type> validAddIns = new SortedList<string, Type>();
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly assembly in assemblies)
            {
                try
                {
                    Type[] types = assembly.GetTypes();
                    foreach (Type type in types)
                    {
                        switch (filter)
                        {
                            case CompositeLoaderFilter.ImplementsInterface:
                            if (type.GetInterface(addInType.Name) != null)
                                validAddIns.Add(type.FullName, type);
                            break;

                            case CompositeLoaderFilter.InheritsBaseClass:
                            if (type.BaseType == addInType)
                                validAddIns.Add(type.FullName, type);
                            break;
                        }
                    }
                }
                catch (FileLoadException flex)
                {
                    Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0} MyNamespace.CompositeManager: {1}", DateTime.Now.ToString(), flex.Message));
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0} MyNamespace.CompositeManager: {1}", DateTime.Now.ToString(), ex.Message));
                }
            }
            return validAddIns;
        }

        internal SortedList<string, Type> RemoteGetServices(string addInPath, string addInSearchPattern, SearchOption addInSearchOption, Type addInType, CompositeLoaderFilter filter)
        {
            string[] files = Directory.GetFiles(addInPath, addInSearchPattern, addInSearchOption);
            SortedList<string, Type> validAddIns = new SortedList<string, Type>();

            if (files.Length > 0)
            {
                foreach (string file in files)
                {
                    if (String.CompareOrdinal(addInPath, file) != 0)
                    {
                        try
                        {
                            Assembly assembly = Assembly.LoadFrom(file);
                            Type[] types = assembly.GetTypes();
                            foreach (Type type in types)
                            {
                                switch (filter)
                                {
                                    case CompositeLoaderFilter.ImplementsInterface:
                                    if (type.GetInterface(addInType.Name) != null)
                                        validAddIns.Add(type.FullName, type);
                                    break;

                                    case CompositeLoaderFilter.InheritsBaseClass:
                                    if (type.BaseType == addInType)
                                        validAddIns.Add(type.FullName, type);
                                    break;
                                }
                            }
                        }
                        catch (FileLoadException flex)
                        {
                            Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0} MyNamespace.CompositeManager: {1}", DateTime.Now.ToString(), flex.Message));
                        }
                        catch (Exception ex)
                        {
                            Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0} MyNamespace.CompositeManager: {1}", DateTime.Now.ToString(), ex.Message));
                        }
                    }
                }
            }

    #if DEBUG
            DebugLoadedAssemblies();
    #endif

            return validAddIns;
        }

    #if DEBUG
        internal void DebugLoadedAssemblies()
        {
            foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
            {
                //Debug.WriteLine(string.Format("Domain: {0} Assembly: {1}", AppDomain.CurrentDomain.FriendlyName, a.FullName));
            }
        }
    #endif
    }
}

以下是一个示例用法:
Composite.Manager.GetComponents(typeof(IMyService), CompositeLoaderFilter.ImplementsInterface);
IMyService[] services = Composite.Manager.GetObjects<IMyService>();

该类将加载当前文件夹中的所有程序集,并检查它们是否包含与输入参数匹配的类型。由于一旦加载就无法卸载程序集,因此该类将在不同的AppDomain中加载它们,这与单个程序集相反,可以被删除。
为了在您的项目中使用它,您应该按照以下步骤进行:
- 删除对您程序集的(硬)引用 - 实现一个接口来与您的类通信 - 加载实现上述接口的类型 - 通过接口使用您的类
希望对您有所帮助 :)

1
创建一个接口程序集,既可以被调用者和ClassA引用。 移除引用并动态加载程序集并实例化类。
Assembly asm = Assembly.LoadFile(@"C:\classAAssembly.dll");
Type type = asm.GetType("classA");
IClassA a = Activator.CreateInstance(type) as IClassA;

1

添加对另一个库的引用以防止首先加载它? :) - Eugene
没错,也许不是最好的方法,但这种方法可以让你完全不包含主项目的引用。 - Yaugen Vlasau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接