Click here to Skip to main content
15,884,388 members
Articles / Programming Languages / C#
Tip/Trick

Loading Assembly to Leave Assembly File Unlocked

Rate me:
Please Sign up or sign in to vote.
4.91/5 (8 votes)
2 Nov 2014CPOL2 min read 39.9K   19   17
How to load assembly into memory in .NET application to have assembly file unlocked

Problem

If you need to manipulate .NET assembly files (rename, move, etc.) and at the same time load assembly in memory, you may be unpleasantly surprised by the fact that assembly file becomes locked by your application.

Using:

C#
Assembly.LoadFrom(assemblyPath);

or:

C#
Assembly.ReflectionOnlyLoadFrom(assemblyPath);

causes the assembly file to be locked.

I faced this problem creating an Assembly Strong Name Sign Tool when I needed to read assembly information and then rename assembly file.

Thanks to the CodeProject community feedback to this tip, I have found the 3 following solutions.

Solutions

  1. Using Assembly.Load(Byte[]) method
    (Proposed by SpArtA)
    C#
    Assembly.Load(File.ReadAllBytes(assemblyPath));

    In this way, assembly is loaded from new byte array in memory and no actual file lock is made.

    Pros: The simplest solution, requires a single line of code.
    Cons: The assembly location is unknown. You can't use Assembly.Codebase and Assembly.Location

  2. Loading assembly in a separate AppDomain
    (Also described in Sacha Barber's Blog in 2009)

    The only way to unload an assembly from an application domain is by unloading the Application Domain
    Application Domain is a unit of isolation in .NET that is like a subprocess in the process of application.

    The solution is to create a temporary AppDomain, read assembly data in it and then unload the temporary domain from memory.

    First, we need to create a class that holds assembly information.
    Class must be marked as [Serializable] since it travels between domain boundaries.

    C#
    [Serializable]
    public class AssemblyInfo
    {
        public string Name { get; set; }
        public string Version { get; set; }
        public string RuntimeVersion { get; set; }
        public string Platform { get; set; }
    
        public AssemblyInfo()
        {
        }
    
        public AssemblyInfo(AssemblyName assemblyName)
        {
            Name = assemblyName.Name;
            Version = assemblyName.Version.ToString();
            RuntimeVersion = "";
            Platform = assemblyName.ProcessorArchitecture.ToString();
        }
    
        public AssemblyInfo(Assembly assembly)
            : this(assembly.GetName())
        {
            RuntimeVersion = assembly.ImageRuntimeVersion;
        }
    }

    Then, we need to create a proxy class that will do stuff in another domain and return data to the current domain.
    It must inherit from MarshalByRefObject.
    Only classes inherited from MarshalByRefObject can be accessed between different application domain boundaries.

    C#
    public class AssemblyLoader : MarshalByRefObject
    {
        public AssemblyInfo LoadAssemlyInfo(string assemblyPath)
        {
            Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
            return new AssemblyInfo(assembly);
        }
    }

    Then, you create a temporary app domain, load the assembly in it and unload the domain:

    C#
    public static AssemblyInfo GetAssemblyInfo(string assemblyPath, bool lite = false)
    {
        AssemblyInfo assemblyInfo = null;
    
        // create a temporary app domain
        AppDomain tempDomain = AppDomain.CreateDomain("TempDomain");
    
        // create proxy instance in temporary domain
        var asmLoader = (AssemblyLoader)tempDomain.CreateInstanceAndUnwrap(
            typeof(AssemblyLoader).Assembly.FullName, typeof(AssemblyLoader).FullName);
    
        // load assembly in other domain
        assemblyInfo = asmLoader.LoadAssemlyInfo(assemblyPath);
    
        // unload temporary domain and free assembly resources
        AppDomain.Unload(tempDomain);
    
        return assemblyInfo;
    }

    Pros: You have access to full assembly info including Assembly.Codebase and Assembly.Location
    Cons: Method requires much more code including 3 separate classes.

  3. Configuring Current AppDomain to enable Shadow Copying Assemblies
    C#
    var setup = AppDomain.CurrentDomain.SetupInformation;
    setup.ApplicationBase = _basePath;
    setup.ShadowCopyFiles = "true";
    var domain = AppDomain.CreateDomain("MyDomainName" + Guid.NewGuid(), null, setup);

    (Proposed by SpArtA)

    This seems a "natural" way provided by .NET.
    As said in MSDN article:

    This setting causes all assemblies in the application path to be copied to a download cache before they are loaded.
    The copy is locked, but the original assembly file is unlocked and can be updated.
    The Common Language Runtime automatically deletes the files when they are no longer needed.

    Optionally set a custom location for shadow copied files by using the CachePath property and the ApplicationName property.

    The base path for the location is formed by concatenating the ApplicationName property to the CachePath property as a subdirectory. Assemblies are shadow copied to subdirectories of this path, not to the base path itself.

    Pros: Requires much less code than method 2 but a bit more than method 1
    Cons: You need to consider Startup Performance issues mentioned in MSDN article

Hope this tip will help you in your projects.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Quipu GmbH
Ukraine Ukraine
My primary job is in development and maintainance of CutomWare.NET Core Banking application which was designed for use in ProCredit Holding.

This is a large, flexible and constantly improving system which includes a huge set of modules for Front and Back-end, payment systems, loans, deposits and whatever other the Bank may need.

Application is contributed with development of several Quipu GmbH regional offices in Kiev, Moscow, Skopje, Frankfurt, San Salvador, Bogota and Accra.

Comments and Discussions

 
Generalmy time-saver Pin
Southmountain16-Apr-16 12:24
Southmountain16-Apr-16 12:24 
AnswerGood work Pin
Sean Feldman3-Nov-14 3:17
Sean Feldman3-Nov-14 3:17 
GeneralRe: Good work Pin
Sacha Barber3-Nov-14 11:15
Sacha Barber3-Nov-14 11:15 
Questiontakes me back to 2009 Pin
Sacha Barber2-Nov-14 5:40
Sacha Barber2-Nov-14 5:40 
Where I did the same thing, see my blog

here's my blog post[^]

Can't believe this is still not fixed
AnswerRe: takes me back to 2009 Pin
Nikita Mazhara2-Nov-14 8:38
Nikita Mazhara2-Nov-14 8:38 
GeneralRe: takes me back to 2009 Pin
Sacha Barber3-Nov-14 11:13
Sacha Barber3-Nov-14 11:13 
GeneralRe: takes me back to 2009 Pin
Nikita Mazhara4-Nov-14 0:22
Nikita Mazhara4-Nov-14 0:22 
GeneralRe: takes me back to 2009 Pin
Sacha Barber4-Nov-14 2:45
Sacha Barber4-Nov-14 2:45 
QuestionEasy way ? Pin
SpArtA2-Nov-14 2:51
SpArtA2-Nov-14 2:51 
AnswerRe: Easy way ? Pin
Sacha Barber2-Nov-14 5:38
Sacha Barber2-Nov-14 5:38 
GeneralRe: Easy way ? Pin
Nikita Mazhara2-Nov-14 8:40
Nikita Mazhara2-Nov-14 8:40 
AnswerRe: Easy way ? Pin
Nikita Mazhara2-Nov-14 8:43
Nikita Mazhara2-Nov-14 8:43 
GeneralRe: Easy way ? Pin
SpArtA2-Nov-14 10:39
SpArtA2-Nov-14 10:39 
GeneralRe: Easy way ? Pin
Nikita Mazhara3-Nov-14 10:33
Nikita Mazhara3-Nov-14 10:33 
GeneralRe: Easy way ? Pin
SpArtA3-Nov-14 22:02
SpArtA3-Nov-14 22:02 
GeneralRe: Easy way ? Pin
SpArtA2-Nov-14 10:46
SpArtA2-Nov-14 10:46 
GeneralRe: Easy way ? Pin
Nikita Mazhara3-Nov-14 10:35
Nikita Mazhara3-Nov-14 10:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.