Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiling a class with another reference class #7

Closed
naweed opened this issue Oct 18, 2022 · 4 comments
Closed

Compiling a class with another reference class #7

naweed opened this issue Oct 18, 2022 · 4 comments

Comments

@naweed
Copy link

naweed commented Oct 18, 2022

Hi,

I am trying to dynamically compile classes. For most part, it is working fine, except when it comes to classes which have reference to another class which was previously compiled. Plz see the example below. Any idea how this can be done.

FdynzMMXoAEkgNr

@martinrhan
Copy link

see the comment of the method CompileClassToType

        // Summary:
        //     This method compiles a class and hands back a dynamic reference to that class
        //     that you can call members on.
        //
        // Parameters:
        //   code:
        //     Fully self-contained C# class
        //
        // Returns:
        //     Instance of that class or null

It should be fully self-contained.
Use CompileAssembly instead

@RickStrahl
Copy link
Owner

RickStrahl commented Nov 27, 2022

Took a look at this today finally. I think this depends on the platform you're running on - on full framework this should work, but on .NET Core it'll fail as written.

The reason for .NET Core failing is that the assembly assignment isn't working based on the dynamically generated type.

script.AddAssembly(deptClassType)

isn't working because the Assembly.Location for this type is empty. The workaround for this is that you have to generate an assembly on disk by specifying a GenerateOutputAssembly for the compilation. You also have to specify a GeneratedClassName (which is used for the modulename).

The following works:

        [TestMethod]
        public void TwoDynamicClassesTest()
        {
            var class1Code = @"
using System;

namespace Test1 {
    public class Person
    {
        public string Name {get; set; } = ""Rick"";
        public string Description {get; set; } = ""Testing"";
    } 
}
";

            var class2Code = @"
using System;
using Test1;

namespace Test
{

    public class Customer
    {
        public Test1.Person CustomerInfo {get; set; } = new Test1.Person();
        public string CustomerNumber  { get; set; }         
    } 
}
";
            var script = new CSharpScriptExecution();
            script.AddLoadedReferences();

            // THESE TWO ARE IMPORTANT! Unique names and physical DLL so it can be referenced
            script.GeneratedClassName = "__person";   
            script.OutputAssembly = @"c:\temp\person.dll"; 

            var personType = script.CompileClassToType(class1Code);
            var person = Activator.CreateInstance(personType);


            Assert.IsNotNull(person, "Person should not be null. " + script.ErrorMessage + "\n" + script.GeneratedClassCodeWithLineNumbers);
            Console.WriteLine("Location: " + personType.Assembly.Location);
            
            //script = new CSharpScriptExecution();
            //script.AddDefaultReferencesAndNamespaces(); //AddLoadedReferences();
            //script.AddAssembly(script.OutputAssembly);
            

            // THESE TWO ARE IMPORTANT!!!!!
            script.GeneratedClassName = "__customer";
            script.OutputAssembly = null;

            script.AddAssembly(personType);
            var customerType = script.CompileClassToType(class2Code);

            Assert.IsNotNull(customerType, "Customer should not be null. " + script.ErrorMessage + "\n" + script.GeneratedClassCodeWithLineNumbers);
            Console.WriteLine(customerType);
            Console.WriteLine(customerType.Assembly.Location);

            dynamic customer = Activator.CreateInstance(customerType);

            
            Assert.IsNotNull(customer.CustomerInfo.Name, "Customer should not be null");
            Console.WriteLine(customer.CustomerInfo.Name);
        }
    }

Note the GeneratedClassName is used for the module name in this case, since we are providing a class name as part of the code. GeneratedClassName is also used when using 'snippets' that don't include the class name like ExecuteCode() or EvaluateExpression() which generate class wrappers.

@RickStrahl
Copy link
Owner

I bumped this up to the Roslyn repo to see if there might be a solution to get a MetaDataReference from an in-memory compiled assembly/type.

dotnet/roslyn#65627

@RickStrahl
Copy link
Owner

So figured out how to get the code to compile by fixing up the meta data reference. That fixes the compilation issue.

Unfortunately though, it still doesn't work because the top level type can't be intstantiated because the dependent type can't be resolved at runtime. Even though we have a type instance that's already active - apparently the reference is not identical to what exists in memory already from the compilation result.

We still end up with this error:

Could not load file or assembly '__person.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.

I think the bottom line to all of this is that if you want to re-use dynamically compiled assemblies in another compiled context, you have to use an on-disk image. The only other option I see is to use a custom assembly loader and that's even more overhead than having the assembly on disk and managing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants