0

suppose I declare a class like this:

Class tst
  Public Props As New Dictionary(Of String, MyProp)
End Class

and added properties something along these lines:

Dim t As New tst
t.Props.Add("Source", new MyProp(3))

but now want to access it like this:

t.Source

how can I create a getter without knowing the name of the getter?

ekkis
  • 9,804
  • 13
  • 55
  • 105
  • Please explain what use the getter will be if you don't know the name to call it. I'm sure you have a reason. So whatever it is, it would help understand the problem. – Steven Doggart May 12 '12 at 19:57
  • I wasn't sure how to express what I need. basically, ignore the last sentence and focus on a solution to allow the `t.Source` reference without actually having to declare a `Source` property. The reason is that the properties will be created at runtime – ekkis May 12 '12 at 21:32
  • 1
    Right, but what good is a property that's created at runtime? If its not there when it's compiled, it will be unusable by its consumers, except via reflection. – Steven Doggart May 12 '12 at 21:40
  • 1
    The reason I ask is because while it is possible (via dynamically generated and compiled code at runtime), I wouldn't recommend it in most cases. More likely, there is a better way to do what you are trying to do. If, however, that is what you need, I can provide you some sample code, but it will have to wait until tomorrow or Monday when I'm not out and about on my iPhone. – Steven Doggart May 12 '12 at 21:49
  • Thank you SteveDog. I think the issue is I come from and still largely favour non-strongly-typed languages so I find myself often trying to work around the strong typing whereas what I should do is understand how to structure things differently – ekkis May 13 '12 at 22:05

1 Answers1

1

Ok, if you insist on "auto-vivifying", the only way I know of to do something like that is to generate the code as a string, and then compile it at runtime using the classes in the System.CodeDom.Compiler namespace. I've only ever used it to generate complete classes from scratch, so I don't know if you could even get it to work for what need to add properties to an already existing class, but perhaps you could if you compiled extension methods at runtime.

The .NET framework includes multiple implementations of the CodeDomeProvider class, one for each language. You will most likely be interested in the Microsoft.VisualBasic.VBCodeProvider class.

First, you'll need to create a CompilerParameters object. You'll want to fill its ReferencedAssemblies collection property with a list of all the libraries your generated code will need to reference. Set the GenerateExecutable property to False. Set GenerateInMemory to True.

Next, you'll need to create a string with the source code you want to compile. Then, call CompileAssemblyFromSource, passing it the CompilerParameters object and the string of source code.

The CompileAssemblyFromSource method will return a CompilerResults object. The Errors collection contains a list of compile errors, if there are any, and the CompiledAssembly property will be a reference to your compiled library (as an Assembly object). To create an instance of your dynamically compiled class, call the CompiledAssembly.CreateInstance method.

If you're just generating a small amount of code, it's pretty quick to compile it. But if it's a lot of code, you may notice an impact on performance.

Here's a simple example of how to generate a dynamic class containing a single dynamic property:

Option Strict Off

Imports System.CodeDom.Compiler
Imports Microsoft.VisualBasic
Imports System.Text

Public Class Form3
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim code As StringBuilder = New StringBuilder()
        code.AppendLine("Namespace MyDynamicNamespace")
        code.AppendLine("   Public Class MyDynamicClass")
        code.AppendLine("       Public ReadOnly Property WelcomeMessage() As String")
        code.AppendLine("           Get")
        code.AppendLine("               Return ""Hello World""")
        code.AppendLine("           End Get")
        code.AppendLine("       End Property")
        code.AppendLine("   End Class")
        code.AppendLine("End Namespace")
        Dim myDynamicObject As Object = generateObject(code.ToString(), "MyDynamicNamespace.MyDynamicClass")
        MessageBox.Show(myDynamicObject.WelcomeMessage)
    End Sub


    Private Function generateObject(ByVal code As String, ByVal typeName As String) As Object
        Dim parameters As CompilerParameters = New CompilerParameters()
        parameters.ReferencedAssemblies.Add("System.dll")
        parameters.GenerateInMemory = True
        parameters.GenerateExecutable = False
        Dim provider As VBCodeProvider = New VBCodeProvider()
        Dim results As CompilerResults = provider.CompileAssemblyFromSource(parameters, code)
        If results.Errors.HasErrors Then
            Throw New Exception("Failed to compile dynamic class")
        End If
        Return results.CompiledAssembly.CreateInstance(typeName)
    End Function
End Class

Note, I never use Option Strict Off, but for the sake of simplicity in this example, I turned it off so I could simply call myDynamicObject.WelcomeMessage without writing all the reflection code myself.

Calling methods on objects using reflection can be painful and dangerous. Therefore, it can be helpful to provide a base class or interface in a shared assembly which is referenced by both the generated assembly, and the fixed assembly which calls the generated assembly. That way, you can use the dynamically generated objects through a strongly typed interface.

I figured based on your question that you were just more used to dynamic languages like JavaScript, so you were just thinking of a solution using the wrong mindset, not that you really needed to or even should be doing it this way. But, it is definitely useful in some situations to know how to do this in .NET. It's definitely not something you want to be doing on a regular basis, but, if you need to support custom scripts to perform complex validation or data transformations, something like this can be very useful.

Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • 1
    I don't have any example code at the moment and I don't have access to VisualStudio to write some up and test it until Monday. When I do, I'll edit my answer to show a simple example. – Steven Doggart May 12 '12 at 23:55
  • I'm laughing... this is far more complicated than I imagined. I'm tagging your reply as the answer because it is and even though I won't likely be using any code you provide, it's probably a good idea to post it as there will be others that will find it tremendously useful. In fact, I'm sure it's just a matter of time before I run into some other scenario that does merit such extravagance :) and you get many points for your efforts to help. thank you. – ekkis May 13 '12 at 22:08
  • @ekkis I updated my answer and added example code and explanation. – Steven Doggart May 14 '12 at 13:03
  • I knew this was just a matter of time before this proved enormously handy :) one question: will it run with Option Strict On (never mind... I see you wrote on the subject about it so I'll need to play with it a bit) – ekkis Jun 06 '12 at 21:31
  • Yes. If you want to use Option Strict On (which is a very good idea), then you need to either use reflection (e.g. `myDynamicObject.GetType().GetProperty("WelcomeMessage").GetGetMethod().Invoke(myDynamicObject, New Object() {})`, or use an interface or base class. However, if you want to have the obviously much simpler syntax of `myDynamicObject.WelcomeMessage` without an interface or base class, you have to use Object with Option Strict Off. In C# you can accomplish this with the `dynamic` keyword. Unfortunately there is no equivalent in VB (except `Object` when Option Strict is Off). – Steven Doggart Jun 07 '12 at 01:19
  • For an example of using an interface or base class, see my answer to this other question: http://stackoverflow.com/questions/10914484/use-dlr-to-run-code-generated-with-compileassemblyfromsource/10915167#10915167 – Steven Doggart Jun 07 '12 at 01:22
  • Since you seem to lean towards dynamic objects, you may also want to look into the `System.Dynamic` namespace and the `ExpandoObject` class in particular. It's only available in .NET Framework version 4, though. – Steven Doggart Jun 07 '12 at 01:23