{"id":20163761,"url":"https://github.com/emik03/.ddl","last_synced_at":"2026-02-14T00:10:50.648Z","repository":{"id":100294957,"uuid":"524517116","full_name":"Emik03/.DDL","owner":"Emik03","description":"Extended Essay - .DDL Project: Loading assemblies based on runtime logic from compile-time code without any reflection overhead.","archived":false,"fork":false,"pushed_at":"2022-08-13T22:05:49.000Z","size":7330,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-19T16:08:56.918Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Emik03.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-13T22:01:11.000Z","updated_at":"2022-08-13T22:05:52.000Z","dependencies_parsed_at":"2023-04-24T21:10:13.265Z","dependency_job_id":null,"html_url":"https://github.com/Emik03/.DDL","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Emik03/.DDL","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Emik03%2F.DDL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Emik03%2F.DDL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Emik03%2F.DDL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Emik03%2F.DDL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Emik03","download_url":"https://codeload.github.com/Emik03/.DDL/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Emik03%2F.DDL/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275990014,"owners_count":25565653,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-19T02:00:09.700Z","response_time":108,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T00:31:40.639Z","updated_at":"2025-09-19T19:20:17.086Z","avatar_url":"https://github.com/Emik03.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# The .DDL Project\n\nThe .DDL Project (Dynamic Dependency Loading) is a document that aims to explain the effects of dependencies in the programming language C#.\n\nThis document will assume the reader having basic knowledge of the C# language. If you aren't familiar in the language, you may start [at the official documentation page](https://docs.microsoft.com/en-us/dotnet/csharp/). \n\n- [The .DDL Project](#the-ddl-project)\n- [Abstract](#abstract)\n- [Chapter 1: Introduction](#chapter-1-introduction)\n  - [Chapter 1.1: What is an assembly and dependency?](#chapter-11-what-is-an-assembly-and-dependency)\n  - [Chapter 1.2: How do we know what dependencies we are using?](#chapter-12-how-do-we-know-what-dependencies-we-are-using)\n  - [Chapter 1.3: Seeing the effects of loading a different assembly](#chapter-13-seeing-the-effects-of-loading-a-different-assembly)\n  - [Chapter 1.4: Assembly searching](#chapter-14-assembly-searching)\n  - [Chapter 1.5: Strong naming](#chapter-15-strong-naming)\n- [Chapter 2: Handling missing dependencies](#chapter-2-handling-missing-dependencies)\n  - [Chapter 2.1: What happens when a dependency is missing?](#chapter-21-what-happens-when-a-dependency-is-missing)\n  - [Chapter 2.2: Catching the exception](#chapter-22-catching-the-exception)\n  - [Chapter 2.3: The DDL Pattern](#chapter-23-the-ddl-pattern)\n  - [Chapter 2.4: Delegates and methods](#chapter-24-delegates-and-methods)\n  - [Chapter 2.5: Object](#chapter-25-object)\n  - [Chapter 2.6: Members](#chapter-26-members)\n- [Chapter 3: Unity / Mono](#chapter-3-unity--mono)\n  - [Chapter 3.1: Using the DLL Pattern in Unity](#chapter-31-using-the-dll-pattern-in-unity)\n  - [Chapter 3.2: Dependency Avoidance from Generics in Unity](#chapter-32-dependency-avoidance-from-generics-in-unity)\n  - [Conclusion](#conclusion)\n\n---\n\n# Abstract\n\nThis document discusses dependencies and assemblies in C#, especially regarding how they are loaded, which is whenever a type or method is called with all of its members being loaded into memory. You can therefore hide dependencies behind methods and only invoke them when the dependency is certain. There are some exceptions to the rule depending on the environment which would require some fiddling, but can end up producing libraries that are able to be simultaneously used in conflicting scenarios without requiring a separate build for each one.\n\n---\n\n# Chapter 1: Introduction\n\n## Chapter 1.1: What is an assembly and dependency?\n\nIn C#, a dependency is formed when some piece of code points to another piece of code from another assembly. [Assemblies are collections of types packaged in a `.dll` or `.exe` file](https://docs.microsoft.com/en-us/dotnet/standard/assembly/). Any C# programmer does this, even in the simplest of programs, whether they are aware of it or not. For example, look at this extremely simple \"Hello World!\" program. The rest of this chapter will use a .NET 6.0 Console Application.\n\n```cs\nusing System;\n\nnamespace DDLProject\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n        }\n    }\n}\n```\n\nSurely such a simple program wouldn't need dependencies, but it does. If you look carefully, we are both referencing [`string`](https://docs.microsoft.com/en-us/dotnet/api/system.string) and [`Console`](https://docs.microsoft.com/en-us/dotnet/api/system.console), which both come from the [`System`](https://docs.microsoft.com/en-us/dotnet/api/system) namespace. Rather than having to reinvent the wheel, a programmer can use preexisting code to tell the console to print a few words.\n\n## Chapter 1.2: How do we know what dependencies we are using?\n\nFortunately, checking which dependencies are loaded is very easy thanks to the combination of the [`AppDomain`](https://docs.microsoft.com/en-us/dotnet/api/system.appdomain) class and [`System.Reflection`](https://docs.microsoft.com/en-us/dotnet/api/system.reflection) namespace. In the following demonstration, we will get the current domain using [`CurrentDomain`](https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.currentdomain) and then get all assemblies with [`GetAssemblies()`](https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.getassemblies). We will use a [`foreach`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/statements/iteration-statements#the-foreach-statement) loop to iterate each one and print out its name by converting it to [`AssemblyName`](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assemblyname) with [`GetName()`](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getname) and using the property [`Name`](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assemblyname.name).\n\n```cs\nusing System;\nusing System.Reflection;\n\nnamespace DDLProject\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n\n            Assembly[] assemblies = AppDomain\n                .CurrentDomain\n                .GetAssemblies();\n\n            foreach (Assembly? assembly in assemblies)\n            {\n                string name = assembly\n                    .GetName()\n                    .Name;\n\n                Console.WriteLine(name);\n            }\n        }\n    }\n}\n```\n\nRunning this code produces the following output:\n\n```\nHello World!\nSystem.Private.CoreLib\nDDLProject\nSystem.Runtime\nSystem.Console\nSystem.Threading\nSystem.Text.Encoding.Extensions\n```\n\n## Chapter 1.3: Seeing the effects of loading a different assembly\n\nWe are able to see that seven different dependencies are at play here, mostly essential assemblies for Console Applications. Now, we will add a bit of code that will reference the [`Regex`](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex) class, which comes from a different assembly.\n\n```cs\nusing System;\nusing System.Reflection;\nusing System.Text.RegularExpressions;\n\nnamespace DDLProject\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n\n            _ = new Regex(\"foo\");\n\n            Assembly[] assemblies = AppDomain\n                .CurrentDomain\n                .GetAssemblies();\n\n            foreach (Assembly? assembly in assemblies)\n                Console.WriteLine(assembly.GetName().Name);\n        }\n    }\n}\n```\n\nNow the code output is as follows:\n\n```\nHello World!\nSystem.Private.CoreLib\nDDLProject\nSystem.Runtime\nSystem.Console\nSystem.Text.RegularExpressions\nSystem.Threading\nSystem.Text.Encoding.Extensions\nSystem.Collections\n```\n\nAs you can see, there are now two new assemblies which have been loaded, [`System.Text.RegularExpressions`](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions), and [`System.Collections`](https://docs.microsoft.com/en-us/dotnet/api/system.collections). This makes sense, [`Regex`](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex) comes from [`System.Text.RegularExpressions`](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions), and the [`Regex`](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex) class has a reference to [`System.Collections`](https://docs.microsoft.com/en-us/dotnet/api/system.collections), shown [here](https://referencesource.microsoft.com/#system/regex/system/text/regularexpressions/Regex.cs,14). As such, both assemblies are loaded into the [`AppDomain`](https://docs.microsoft.com/en-us/dotnet/api/system.appdomain).\n\nFinally, the following example shows a user-defined Class Library, followed by the usage of this library, demonstrating that the user-defined assembly is also added.\n\nThe library:\n\n```cs\nnamespace Dependency\n{\n    public class Foo\n    {\n    }\n}\n```\n\nThe program:\n\n```cs\nusing System;\nusing System.Reflection;\nusing Dependency;\n\nnamespace DDLProject\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Hello World!\");\n\n            _ = new Foo();\n\n            Assembly[] assemblies = AppDomain\n                .CurrentDomain\n                .GetAssemblies();\n\n            foreach (Assembly? assembly in assemblies)\n                Console.WriteLine(assembly.GetName().Name);\n        }\n    }\n}\n```\n\nThe output:\n\n```\nHello World!\nSystem.Private.CoreLib\nDDLProject\nSystem.Runtime\nSystem.Console\nDependency\nSystem.Threading\nSystem.Text.Encoding.Extensions\n```\n\nAs shown here, `Dependency` has been added as a dependency because we are creating a `Foo` object which comes from that assembly. While this may seem obvious, it is vital to understand the basics before diving into the next chapters.\n\n## Chapter 1.4: Assembly searching\n\nWhen an assembly is referenced, C# will automatically search for the file, and load it into memory. Once loaded, an assembly cannot be unloaded. You can isolate dependencies by creating a new [AppDomain](https://docs.microsoft.com/en-us/dotnet/api/system.appdomain) and loading the dependency in there as a workaround. The language will first search through the global assembly cache, then the directory of the running application and any subdirectories it may have until it finds the assembly. An important note is that regardless of namespace or type declarations, it will search for the specific assembly, and not get fooled by similar declarations. Consider the following example.\n\nThe first library:\n\n```cs\nnamespace Dependency\n{\n    public class Foo\n    {\n        public string Method() =\u003e \"First library\";\n    }\n}\n```\n\nThe second library:\n\n```cs\nnamespace Dependency\n{\n    public class Foo\n    {\n        public string Method() =\u003e \"Second library\";\n    }\n}\n```\n\nBoth assemblies will have different project names, but have identical declarations, with the only difference being that the method returns different strings. Now with both of them compiled, let's add the first one to our project.\n\n```cs\nusing System;\nusing System.Reflection;\nusing Dependency;\n\nnamespace DDLProject\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(new Foo().Method());\n        }\n    }\n}\n```\n\nNow unsurprisingly, if we compile this program and only include the first dependency, then the output will be as expected.\n\nThe output:\n\n```\nFirst library\n```\n\nWhen we include both dependencies, we still get the same result.\n\nThe output:\n\n```\nFirst library\n```\n\nThis is the crucial part though, if we only include the other dependency, then we get an error.\n\nThe output:\n\n```\nUnhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'FirstDependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The specified module could not be found.\nFile name: 'FirstDependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'\n```\n\nAs shown here, namespaces and types contribute nothing for conflicts. That is unless both assemblies have the same name and metadata, in that case, the first two tests will pass, but something changes drastically in the third test.\n\nThe output:\n\n```cs\nSecond library\n```\n\nIt uses the second library because it has the same name, and therefore thinks it is the library to use. The only reason that the second example succeeded was that it was able to find the first library before the second one. Now this can be a very useful tool, and is the default behavior of libraries. However, it can allow others to spoof your dependencies as shown in the earlier example. The simplest way to resolve this is to make your library strongly named.\n\n## Chapter 1.5: Strong naming\n\nFor more details about how it searches the assembly, you can [visit this page](https://docs.microsoft.com/en-us/dotnet/framework/deployment/how-the-runtime-locates-assemblies).\n\nStrong-naming is an extended way to sign your assembly. Alongside the name of the assembly, a digital signature is generated from a private key created by the maintainers. The version number, culture information, and public key are also added as part of the identification of the library. There are two main purposes of using a strong-named library.\n\n1. To prevent others from overriding your library which could potentially contain malicious code.\n2. To allow others from loading multiple versions of the same library.\n\nTo demonstrate the effects, you can create a class library that will be strongly named with the following simple code.\n\n```cs\nnamespace Dependency\n{\n    public class Foo\n    {\n        public string Method() =\u003e \"First library\";\n    }\n}\n```\n\nBuild the library, and then rename the exported `dll` in the `bin` folder so that it won't get overridden later. From there, we can change our code. Change the return of `Foo.Method()` to be anything else. In this example it will be `Second library`. Before compiling, change some part of the signature, such as the version number. Once both libraries are compiled, create a project and reference either dependency. Make sure to disable `Copy Local` for said dependency. From there, build, copy the other dependency, and test the application. The program will throw a [`FileNotFoundException`](https://docs.microsoft.com/en-us/dotnet/api/system.io.filenotfoundexception?view=net-6.0) even if you renamed the file to be the same as the original due to the different signatures that they have.\n\n# Chapter 2: Handling missing dependencies\n \n## Chapter 2.1: What happens when a dependency is missing?\n\nNow as the name says, a dependency depends on the existence of other code. However, there are situations in which an assembly isn't available or present. Let's go back to the final example shown from Example 1.3 and build the code an an `.exe` file, except this time we remove the `Dependency.dll` file.\n\nOpening the application results in the following error:\n\n```\nUnhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The specified module could not be found.\nFile name: 'Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'\n   at DDLProject.Program.Main(String[] args)\n```\n\nUnsurprisingly, we see a [`FileNotFoundException`](https://docs.microsoft.com/en-us/dotnet/api/system.io.filenotfoundexception) being thrown.\n\n## Chapter 2.2: Catching the exception\n\nNow naturally, as an exception is thrown, we might want to catch the exception and do something different. Let's wrap the previous example with a try-catch.\n\n```cs\nusing System;\nusing Dependency;\n\nnamespace DDLProject\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Outside of the try-catch.\");\n            \n            try\n            {\n                Console.WriteLine(\"Inside of the try-catch.\");\n            \n                _ = new Foo();\n            }\n            catch (FileNotFoundException)\n            {\n                Console.WriteLine(\"Caught!\");\n            }\n            \n            Console.ReadLine();\n        }\n    }\n}\n```\n\nHowever, the exception is still thrown. Output:\n\n```\nUnhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The specified module could not be found.\nFile name: 'Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'\n   at DDLProject.Program.Main(String[] args)\n```\n\nGiven that none of the [`WriteLine`](https://docs.microsoft.com/en-us/dotnet/api/system.console.writeline) statements were called, this tells us that the exception is thrown at latest the method invoke. In that case, let's extract the instantiation of `Foo` into a method, and call the method inside a try-catch.\n\n```cs\nusing System;\nusing Dependency;\n\nnamespace DDLProject\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.WriteLine(\"Outside of the try-catch.\");\n            \n            try\n            {\n                Console.WriteLine(\"Inside of the try-catch.\");\n                Dependency();\n            }\n            catch (FileNotFoundException)\n            {\n                Console.WriteLine(\"Caught!\");\n            }\n\n            Console.ReadLine();\n        }\n\n        private static void Dependency()\n        {\n            Console.WriteLine(\"We shouldn't see this.\");\n\n            _ = new Foo();\n        }\n    }\n}\n```\n\nNow, the output of the program is as follows:\n\n```\nOutside of the try-catch.\nInside of the try-catch.\nCaught!\n```\n\nAs you can see, the program now correctly halts as it falls into the `catch` block. We can see precisely that the first part of the try-catch was able to be done, but none of the statements in `Dependency` were able to run because the method invoke caused a [`FileNotFoundException`](https://docs.microsoft.com/en-us/dotnet/api/system.io.filenotfoundexception) to be thrown.\n\n## Chapter 2.3: The DDL Pattern\n\nWhile a try-catch can certainly be used, there are drawbacks. For instance, a `FileNotFoundException` is not exclusive to a missing dependency, try-catches also by nature are not very performant. Fortunately, there are ways to determine the existence of assemblies and execute code as such. First, create a class ([`static`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/static), [`sealed`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/sealed), or otherwise) that contains an enum of dependencies which includes none, a public getter for this enum, and a static constructor.\n\n```cs\nusing System;\n\nnamespace DDLProject\n{\n    public static class DDLPatternExample\n    {\n        public enum Dependencies\n        {\n            None, Dependency\n        }\n\n        static DDLPatternExample() { }\n\n        public static Dependencies Dependency { get; }\n    }\n\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.ReadLine();\n        }\n    }\n}\n```\n\nThe enum type allows you to later expand with more dependencies if needed, such as different dependencies based on the operating system of the end user. Next, we will make the static constructor determine which assembly to use.\n\n```cs\nusing System;\nusing System.Linq;\n\nnamespace DDLProject\n{\n    public static class DDLPatternExample\n    {\n        public enum Dependencies\n        {\n            None, Dependency\n        }\n\n        static DDLPatternExample() =\u003e Dependency = AppDomain\n            .CurrentDomain\n            .GetAssemblies()\n            .Any(s =\u003e s.GetName().FullName == \"Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\")\n            ? Dependencies.Dependency\n            : Dependencies.None;\n\n        public static Dependencies Dependency { get; }\n    }\n\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.ReadLine();\n        }\n    }\n}\n```\n\nAs we only have 2 members in our enum, for now we can make it extremely bare bone and only evaluate whether any loaded assembly has the same name as our dependency. Keep in mind that this means the class will only determine whether the library has been successfully loaded into memory when the static constructor is called, which would be the first time that this type is called. We can later expand it in the event that more dependencies are needed. Now, for each member that we want to access, we will be creating N+1 methods, where N is the number of dependencies. We will go back to our `Dependency` project and add a method that returns some value for demonstration.\n\n```cs\nnamespace Dependency\n{\n    public class Foo\n    {\n        private static int s_id;\n\n        public int CurrentId() =\u003e ++s_id;\n    }\n}\n```\n\nSuppose we want to invoke `CurrentId` without resorting to a try-catch, we will first add a `CurrentIdInner` method in our class that calls the method directly.\n\n```cs\nusing System;\nusing System.Linq;\nusing Dependency;\n\nnamespace DDLProject\n{\n    public static class DDLPatternExample\n    {\n        public enum Dependencies\n        {\n            None, Dependency\n        }\n\n        static DDLPatternExample() =\u003e Dependency = AppDomain\n            .CurrentDomain\n            .GetAssemblies()\n            .Any(s =\u003e s.GetName().FullName == \"Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\")\n            ? Dependencies.Dependency\n            : Dependencies.None;\n\n        public static Dependencies Dependency { get; }\n\n        private static int CurrentIdInner() =\u003e new Foo().CurrentId();\n    }\n\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.ReadLine();\n        }\n    }\n}\n```\n\nAccessibility here is crucial, make sure this method is private! If you allow other classes to run the method then you risk a [`FileNotFoundException`](https://docs.microsoft.com/en-us/dotnet/api/system.io.filenotfoundexception), or loading it into memory when you don't want to. What will be done instead is we add a method that asks what `Dependency` is, and run the method accordingly. We will use a switch expression due to its extremely light syntax, but you can use a regular switch or if/else-if/else pattern instead.\n\n```cs\nusing System;\nusing System.Linq;\nusing Dependency;\n\nnamespace DDLProject\n{\n    public static class DDLPatternExample\n    {\n        public enum Dependencies\n        {\n            None, Dependency\n        }\n\n        static DDLPatternExample() =\u003e Dependency = AppDomain\n            .CurrentDomain\n            .GetAssemblies()\n            .Any(s =\u003e s.GetName().FullName == \"Dependency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\")\n            ? Dependencies.Dependency\n            : Dependencies.None;\n\n        public static Dependencies Dependency { get; }\n\n        public static int CurrentId() =\u003e Dependency switch\n        {\n            Dependencies.None =\u003e 0,\n            Dependencies.Dependency =\u003e CurrentIdInner(),\n            _ =\u003e throw new NotImplementedException($\"The value of {nameof(Dependency)} hasn't been implemented: {Dependency}.\")\n        };\n\n        private static int CurrentIdInner() =\u003e new Foo().CurrentId();\n    }\n\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Console.ReadLine();\n        }\n    }\n}\n```\n\nNow, `CurrentId` is what we call instead when we need to access the value. Based on what dependency is, it will return `0` for no dependency, `Foo.CurrentId` for the dependency, and throw otherwise. The reason this won't throw is because as long as the method which contains the dependency isn't invoked, C# will continue to run your method. Making method calls unreachable allows us to keep our application from loading the assemblies until we are certain that they exist.\n\n## Chapter 2.4: Delegates and methods\n\nIn a similar way that you can hide dependencies by using methods, you can do the same with delegates and events since they also act essentially as methods. This is really powerful since you are able to pass in delegates back-and-forth and still have full control on which dependencies are loaded. The following demonstration shows an object `Foo` created inside an [`Action`](https://docs.microsoft.com/en-us/dotnet/api/system.action). The console will print `true` only if `Dependency` is loaded into memory, otherwise `false`.\n\n```cs\nusing System;\nusing System.Linq;\nusing Dependency;\n\nnamespace DDLProject\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Action a = () =\u003e _ = new Foo();\n\n            Console.WriteLine(AppDomain\n                .CurrentDomain\n                .GetAssemblies()\n                .Select(s =\u003e s.GetName().Name)\n                .Contains(\"Dependency\"));\n        }\n    }\n}\n```\n\nThe output:\n\n```\nfalse\n```\n\nUnsurprisingly, if you invoke this method, as demonstrated in [Chapter 2.2: Catching the exception](#chapter-22-catching-the-exception), because the method contains a member that requires a reference to the `Dependency` assembly, it will have to load it into memory.\n\n```cs\nusing System;\nusing System.Linq;\nusing Dependency;\n\nnamespace DDLProject\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Action a = () =\u003e _ = new Foo();\n            a();\n\n            Console.WriteLine(AppDomain\n                .CurrentDomain\n                .GetAssemblies()\n                .Select(s =\u003e s.GetName().Name)\n                .Contains(\"Dependency\"));\n        }\n    }\n}\n```\n\nThe output:\n\n```\ntrue\n```\n\n## Chapter 2.5: Object\n\nThe type [`object`](https://docs.microsoft.com/en-us/dotnet/api/system.object) — being able to store any value — can store the values of another dependency. The important aspect of this however is that [`object`](https://docs.microsoft.com/en-us/dotnet/api/system.object) can be used to hide the fact that you are intending to pass values from the other dependencies in which you don't want to load until called. The following example shows 3 different methods, a defined method in a class, an encapsulated [`Func{T, TResult}`](https://docs.microsoft.com/en-us/dotnet/api/system.func-2), and an implementation of a user-defined method that takes in an [`object`](https://docs.microsoft.com/en-us/dotnet/api/system.object) and returns an [`object`](https://docs.microsoft.com/en-us/dotnet/api/system.object). All of them cast the value to `Foo`, use one of its methods, and then returns it as `Foo`. As long as we do not call any of these methods, C# will not load any of the dependencies.\n\n```cs\nusing System;\nusing System.Linq;\nusing Dependency;\n\nnamespace DLLProject\n{\n    public delegate object Test(object foo);\n\n    class Program\n    {\n        public object Method(object foo)\n        {\n            Console.WriteLine(((Foo)foo).CurrentId());\n            return (Foo)foo;\n        }\n\n        static void Main(string[] args)\n        {\n            Func\u003cobject, object\u003e action = foo =\u003e\n            {\n                Console.WriteLine(((Foo)foo).CurrentId());\n                return (Foo)foo;\n            };\n\n            Test test = foo =\u003e\n            {\n                Console.WriteLine(((Foo)foo).CurrentId());\n                return (Foo)foo;\n            };\n\n            Console.WriteLine(AppDomain\n                .CurrentDomain\n                .GetAssemblies()\n                .Select(s =\u003e s.GetName().Name)\n                .Contains(\"Dependency\"));\n        }\n    }\n}\n```\n\nThe output:\n\n```\nfalse\n```\n\n## Chapter 2.6: Members\n\nClasses and structs work similarly to methods in that the first time they are called will result in loading all of the assemblies needed to represent the class. This means that dependency will attempt to load if it is included in a method signature, as a field/event/property. The class itself as well as its members can be [`static`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/static) or not as it handles both cases identically. The following example demonstrates that even mutating a non-`Foo` value within a static class causes the dependency to load.\n\n```cs\nusing System;\nusing System.Linq;\nusing Dependency;\n\nnamespace DLLProject\n{\n    internal static class Test\n    {\n        internal static int _i;\n\n        internal static List\u003cFoo\u003e s_foo = new();\n\n        internal static Foo F(Foo f) =\u003e f;\n\n        internal static Foo R =\u003e new();\n    }\n\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            Test._i++;\n\n            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();\n\n            Console.WriteLine(assemblies.Select(s =\u003e s.GetName().Name).Contains(\"Dependency\"));\n        }\n    }\n}\n```\n\nThe output:\n\n```\ntrue\n```\n\n# Chapter 3: Unity / Mono\n\n## Chapter 3.1: Using the DLL Pattern in Unity\n\nThe DDL pattern showed a way to handle dependencies automatically depending on the environment, however the problem was that you would have to somehow determine the existence of the assembly in the first place. In Unity though, one option definitely makes this pattern very useful, notably [`Application.isEditor`](https://docs.unity3d.com/ScriptReference/Application-isEditor.html), which determines whether the code is being run in the Unity Editor or is compiled as an executable runtime. One example where you could use this is if you are creating a Unity project for another Unity-based project, and require referencing the `Assembly-CSharp.dll` file.\n\n`Assembly-CSharp.dll` is a file generated by Unity that contains the entire codebase of the project, the assembly name is hardcoded and cannot be changed, neither are they strongly named as shown in [Chapter 1.5: Strong naming](#chapter-15-strong-naming), this means that trying to import the dll into the [Plugins](https://docs.unity3d.com/Manual/Plugins.html) folder will cause a conflict between it and the generated assembly from your own scripts.\n\nTo circumvent this, you first create a [Class Library](https://docs.microsoft.com/en-us/dotnet/core/tutorials/library-with-visual-studio) project and add the desired `Assembly-CSharp.dll` reference. From there, use the DDL pattern as described in \n[Chapter 2.3: The DDL Pattern](#chapter-23-the-ddl-pattern).\n\nSuch an implementation may be abstracted as the following code:\n\n```cs\nusing System;\nusing Assets.Scripts;\nusing UnityEngine;\n\nnamespace DLLProject\n{\n    public static class Dependency\n    {\n        public static void Access(Action game, Action otherwise = null)\n        {\n            if (Application.isEditor)\n            {\n                game();\n                return;\n            }\n\n            otherwise?.Invoke();\n        }\n\n        public static T Access\u003cT\u003e(Func\u003cT\u003e game, Func\u003cT\u003e otherwise = null)\n            =\u003e Application.isEditor ? game() : otherwise?.Invoke();\n    }\n}\n```\n\nWith a method such as this, you can take advantage of the concepts introduced in [Chapter 2.4: Delegates and methods](#chapter-24-delegates-and-methods) and access any game dependency from within the library, provided that you always use this method first.\n\n```cs\nint score = Dependency.Access(() =\u003e Player.Instance.Score);\n```\n\n## Chapter 3.2: Dependency Avoidance from Generics in Unity\n\nAn extremely important edge case are generics. In order for Mono to compile your code in the Unity Editor, it first scans through the entire codebase for all the required types. \n\nIf you are returning something from an assembly you cannot yet access, callbacks and generics can have rather unpredictable behavior when trying to use DDL.\n\nThe following code will throw errors when entering in Unity.\n\n```cs\nusing System;\nusing Assets.Scripts;\n\nnamespace DDLProject\n{\n    internal class Test\n    {\n        private static void Unused()\n        {\n            InaccessibleComponent component = null;\n\n            Action uhOh = () =\u003e Debug.Log(component);\n        }\n    }\n}\n```\n\nThis is because of the action `uhOh`. This is a method that takes no parameters, yet is able to access `component` despite that. To circumvent that, C# will generate a display class during compilation. This means that any variable passed into a closure will generate a class that will contain a member to that reference.\n\n```cs\n[CompilerGenerated]\nprivate sealed class \u003c\u003ec__DisplayClass0_0\n{\n    // This auto-generated line is what causes issues!\n    public InaccessibleComponent component;\n\n    internal void \u003cM\u003eb__0()\n    {\n        Debug.Log(component);\n    }\n}\n```\n\nFortunately, we can circumvent this with casting.\n\n```cs\nusing System;\nusing Assets.Scripts;\n\nnamespace DDLProject\n{\n    internal class Test\n    {\n        private static void Unused()\n        {\n            object component = (InaccessibleComponent)null;\n\n            Action uhOh = () =\u003e Debug.Log((InaccessibleComponent)component);\n        }\n    }\n}\n```\n\nThis will cause the auto-generated member `component` in `\u003c\u003ec__DisplayClass0_0` to be of type [`object`](https://docs.microsoft.com/en-us/dotnet/api/system.object) as oppose to `InaccessibleComponent`, which works around this issue as Mono will have to dismiss the dependency as [`object`](https://docs.microsoft.com/en-us/dotnet/api/system.object) from `mscorlib` rather than whatever dependency `InaccessibleComponent` comes from.\n\nFor Unity, you can generally use non-generic method overloads and then cast the result. This is extremely useful regarding Unity API calls. Overhead will occur due to the capture of `this` within the closure, but is still faster than `System.Reflection`.\n\n```cs\nobject inaccessibleComponent = Dependency.Access(() =\u003e\n{\n    object comp = GetComponent(typeof(InaccessibleComponent));\n\n    InaccessibleComponent inaccessible = (InaccessibleComponent)inaccessible;\n\n    inaccessible.AccessAnyPropertyHere = \"This is totally fine!\";\n\n    return inaccessible;\n});\n```\n\n## Conclusion\n\nThis paper extensively documented the way that dependencies are handled during the runtime execution of C# code. As C# code is compiled down to IL (Intermediate Language), this should theoretically be applicable to other IL-based languages such as Visual Basic and F#. As I lack the experience for those languages, I am unable to confirm for sure whether there are any other hoops involved such as potential quirks with the compiler.\n\nC# being a high-level language means that a lot of these small details are intentionally abstracted away from you. In a majority of situations, what the language does for you is correct. However, the language still allows fine control over low-level concepts. The DDL Pattern is on its own a low-level way of handling dependencies, the same way that [pointers](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code) are a low-level way of handling unmanaged memory.\n\nThe DDL Pattern on its own is situational, and perhaps not as applicable as other design patterns like the [Visitor Pattern](https://en.wikipedia.org/wiki/Visitor_pattern). Using the pattern on a test project on its own does give you good insight into how concepts like dependencies work, and the flexibility of such a pattern can encourage you to employ it into more serious projects to significantly improve performance by avoiding use of excessive reflection.\n\nFor an example of how the DDL Pattern can be integrated practically, refer to [this repository](https://github.com/Emik03/Pakala).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femik03%2F.ddl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femik03%2F.ddl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femik03%2F.ddl/lists"}