Autovivified properties? - vb.net

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?

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.

Related

Replacing an object by a deserialized version of it, and preserving references

Say I have an object of my custom class, called AppSettings, which has various properties that hold both value types (integers, doubles, strings, etc.) and reference types (arrays, other custom objects, etc.). Some of these custom objects have their own custom objects, so the path down to some of the value type properties can go very deep.
For example:
<Serializable()>
Public Class AppSettings
Public Property windowHeight As Integer = 600
Public Property windowWidth As Integer = 800
Public Property defaultLengthUnit As Unit = Units.meters
Public Property defaultAngleUnit As Unit = Units.degrees
End Class
Where Unit class is defined as:
<Serializable()>
Public Class Unit
Public Property Name As String
Public Property Abbreviation As String
Public Property Scale As Double
End Class
And Units module is defined as:
Public Module Units
Public meters As New Unit With {
.Name = "Meters",
.Abbreviation = "m.",
.Scale = 1
}
Public degrees As New Unit With {
.Name = "Degrees",
.Abbreviation = "°",
.Scale = 1
}
End Module
Some other code might refer or bind to some of the reference type properties, or their internal properties. Now, let's say I provide a way for the user to save current state of AppSettings by serializing it into XML:
Public Sub SerializeAppSettings(ByVal filename As String)
Using sw As StreamWriter = New StreamWriter(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
xmls.Serialize(sw, appSettings)
End Using
End Sub
and then load them back (by deserializing) at any time while running the application:
Public Function DeserializeAppSettings(ByVal filename As String) As AppSettings
If Not File.Exists(filename) Then Return Nothing
Using sr As StreamReader = New StreamReader(filename)
Dim xmls As XmlSerializer = New XmlSerializer(GetType(AppSettings))
Return TryCast(xmls.Deserialize(sr), AppSettings)
End Using
End Function
It is called like so:
AppSettings = DeserializeAppSettings(settingsFilePath)
The problem here is that all the references to AppSettings that other objects and bindings have, are now broken, because deserialization replaces the old instance of AppSettings with a completely new instance, and the references are not transferred to it.
It appears that this doesn't break references to value-type properties (like windowHeight, which is Integer), but it definitely breaks references to reference-type properties, like defaultLengthUnit. So for example, if some other object or WPF control is referring/binding to, say, AppSettings.defaultLengthUnit.scaleToBaseUnit, it doesn't work anymore.
I wonder, how can I fix this, so that deserialization would replace the old instance of AppSettings and transfer all the references from it to the new instance that it generated?
As I understand it, there are three ways to go about it:
Replace the old instance with an new one in the exact same memory allocation, with the same internal ID, which would probably be too hacky, and I'm not sure if at all possible.
Another way would be for the DeserializeAppSettings function to overwrite each property value of the current AppSettings instance, one by one, by the deserialized values. However, since some properties of AppSettings are objects, which have their own objects, which have their own objects (and so on), I would basically need to type out all the hierarchy tree in that DeserializeAppSettings function to get down to the value type properties. And every time I would need to add or remove any property in the AppSettings class (or in any class that is used in it's properties), I would also need to manually update the parsing code in DeserializeAppSettings function. This is seriously unmaintainable.
Lastly, it would probably be possible to automate this value replacement through reflection, but reflection is very slow, and generally discouraged if there is any other option.
I hope I am missing something obvious here. Any suggestions on how to transfer all the references to AppSettings when the old instance of it is replaced with a new one through deserialization?
EDIT: Updated the code to include all the relevant classes.

Translating C# functions into vb.net

I need help converting some of this code. Mainly:
private static void SetProvider(ServiceCollection collection)
=> _service = collection.BuildServiceProvider();
and the line below it. This is being used for a discord bot using Discord.Net with the music library Victoria. Can someone also tell me what this actually is? Just a side question. this uses static classes and there's not anything called static on VB.Net so what would be the best call here? I've seen some other posts from here debating whether to use NonInheritable Class or a Module. What are the differences and when it is better to use either one?
It depends on what you want exactly. VB.NET does not provide static classes. Instead, it offers modules, but those are not completely equal to static classes.
The module version would be:
Public Module ServiceManager
Private _service As IServiceProvider
Public Sub SetProvider(collection As ServiceCollection)
_service = collection.BuildServiceProvider()
End Sub
Public Function GetService(Of T As New)() As T
Return _service.GetRequiredService(Of T)()
End Function
End Module
The class version would be:
Public NotInheritable Class ServiceManager
Private Sub New()
End Sub
Private Shared _service As IServiceProvider
Public Shared Sub SetProvider(collection As ServiceCollection)
_service = collection.BuildServiceProvider()
End Sub
Public Shared Function GetService(Of T As New)() As T
Return _service.GetRequiredService(Of T)()
End Function
End Class
When using the class implementation, you have to be careful to mark all members as Shared. Additionally, you can consider the following:
Declare the class as NotInheritable, since neither VB.NET modules nor C# static classes can be inherited from. (The corresponding C# keyword is sealed, by the way, but it will never be used in this context, since C# does support static classes.)
Create one private (default) constructor for the class. That will make sure that you cannot instantiate the class. VB.NET modules nor C# static classes cannot be instantiated either.
Using VB.NET modules is somewhat more straightforward, but keep in mind that VB.NET modules have a little quirk. When accessing a member of a module, you are typically not required to prefix it with the module name. Suppose you have some kind of service class called MyService and you have implemented your ServiceManager as a module. Then you do not need to call it like:
Dim svc As MyService = ServiceManager.GetService(Of MyService)()
Instead, you could just call it like:
Dim svc As MyService = GetService(Of MyService)()`.
When using the former method, Visual Studio actually suggests to simplify the name and change it to the latter method. But when you afterwards add another imported namespace that also happens to contain a module that has a GetService(Of T)() method, you will get an error indicating that GetService is ambiguous, in which case you would be forced to prefix it with the module name (like in the former method).
I personally find this checking behavior in Visual Studio regarding VB.NET module member usage to be rather annoying and confusing. I prefer prefixing calls with the module name (for the sake of writing self-documenting code and avoiding ambiguity as mentioned), but I do not want to disable the "simplify name" hint/suggestion in Visual Studio. So I personally prefer a class implementation instead of a module implementation when implementing something in VB.NET that mimics a C# static class.
Or even better: I would avoid a static class design and switch to a "regular" class design when possible. Using class instances has several advantages, like using composition (which is also an important technique used in many popular behavioral design patterns), simplified mocking/unittesting, and less side effects in general.
The equivalent VB.NET is:
Private Shared Sub SetProvider(collection As ServiceCollection)
_service = collection.BuildServiceProvider()
End Sub
C# expression bodies are just a single expression body method, MS Docs e.g. the following are equivalent:
void Greet()
{
Console.WriteLine("Hello World");
}
// Same as above
void Greet() => Console.WriteLine("Hello World");

Named Constructor Idiom in VB.NET?

Is using the Named Constructor Idiom possible in VB.NET? I've found many examples in C#/C++ but can't quite wrap my head around how to use it in vb.net. Seems like a better method of keeping my code readable when involving a lot of constructors with similar argument types.
I've never heard this term before, but after a quick search it sounds vaguely like the Static Factory Pattern. The idea is you make the constructor private and use a shared (static in c#) public function to create the new object.
Public Class Foo
Private Sub New()
End Sub
Public Shared Function CreateNew(param as Object) as Foo
Dim obj as New Foo()
obj.Prop = param
return obj
End Function
End Class
You sure can make Named Constructors in VB. The pattern uses a static (Shared in VB) factory method on the class itself, so that the method can be named. (Other Factory patterns involve using a separate Factory class to provide the static method.)
System.Drawing.Color is a simple example. The pattern is implemented underneath as a static (Shared) property. Since no arguments are necessary, the Get method of a Property works just fine:
Public Shared ReadOnly Property Chartreuse As Color
Usage:
Dim favoriteColor as Color = Color.Chartreuse
Or you can make static factory methods to do the same thing.
Public Class TheClass
Public Sub New()
End Sub
Public Sub New(input As String)
'do something with input
End Sub
Public Shared Function MyNamedConstructor() As TheClass
Return New TheClass
End Function
Public Shared Function AnotherNamedConstructor() As TheClass
Return New TheClass("Another Name")
End Function
End Class
As for whether this pattern is "better" than overloading constructors, that's really an opinion. Personally, I would just overload the constructors. As you can see in the example above, the constructors need to be there anyway.
I suggest using the Named Constructor pattern when you have only a few possible ways to construct your class/struct, but consumers of your class/struct will be using those few constructors often, and with different input values to those constructors (as in the System.Drawing.Color example).
The Name in 'Named Constructor' doesn't represent a name for the constructor itself, but for the object resulting from the constructor. If your named constructor can be used to create two objects that don't feel right to give the same name to, then don't give the constructor that name.

Convert VB.net Code into C#.net

I am working on a C#.net. I have a code of vb.net and I want to convert those code into C#.
I have done all my task but while running application it gives me a error of object not set to an object reference. below is my VB.net code. I have used third party ddl in my code so ExchangeList is a class of that dll.
Private WithEvents moExchanges As ExchangeList
Private Sub RequestChartData()
Trace.WriteLine("Init")
moExchanges = moHost.MarketData.Exchanges
End Sub
Now below is my C#.code
Private Host moHost;
Private ExchangeList moExchanges;
private void RequestChartData()
{
Trace.WriteLine("Init");
moExchanges = moHost.MarketData.Exchanges;
}
Thanks.
It's not possible to tell you with 100% certainty the source of your object not set to an object reference. without seeing more code, but the error appears to be telling you that moHost is null - in other words, you haven't created an instance of the object yet.
So when you try to call MarketData.Exchanges on the object (so you can assign it to moExchanges, it's throwing the error.
Find moHost in your code and make sure you have that you have assigned an instance of that object to it (by calling it's constructor, like moHost = new Host() or whatever the constructor is), and this should fix your error.
UPDATE
You never initialize moHost. This line:
private Host moHost;
Simply declares the object - at this point it's null. So when you try to access any instance methods/properties, you get the object not set to an object reference error.
You need to create an instance of moHost by calling it's constructor, something like this:
private Host moHost = new Host();
The constructor may require parameters - take a look at the documentation for the third-party DLL (intellisense in the IDE may also tell you what parameters, if any, are needed).
This will take care of your error.
()Instead of:
Private Host moHost;
Private ExchangeList moExchanges;
I think you need to write it as:
Private Host moHost = New Host();
Private ExchangeList moExchanges = New ExhangeList();

How to load a class into the current instance within Sub New

Long term lurker, first time poster here.
I have written a class to model an object in vb.net, using vs2008 and framework 2.0. I am serializing the class to an XML file for persistent storage. I do this with a method in the class like this:
Public Sub SaveAs(ByVal filename As String)
Dim writer As New Xml.Serialization.XmlSerializer(GetType(MyNamespace.MyClass))
Dim file As New System.IO.StreamWriter(filename)
writer.Serialize(file, Me)
file.Close()
End Sub
I now want to do a similar thing but reading the class from file to the current instance, like this:
Public Sub New(ByVal filename As String)
Dim reader = New Xml.Serialization.XmlSerializer(GetType(MyNamespace.MyClass))
Dim file = New System.IO.StreamReader(FullPath)
Me = CType(reader.Deserialize(file), MyNamespace.MyClass)
End Sub
However, I cannot assign anything to “Me”. I’ve tried creating a temporary object to hold the file contents then copying each property and field over to the current instance. I iterated over the properties (using Reflection), but this soon gets messy, dealing with ReadOnly collection properties, for example. If I just copy each property manually I will have to remember to modify the procedure whenever I add a property in the future, so that sounds like a recipe for disaster.
I know that I could just use a separate function outside the class but many built-in .NET classes can instantiate themselves from file e.g. Dim bmp As New Bitmap(filename As String) and this seems very intuitive to me.
So can anyone suggest how to load a class into the current instance in the Sub New procedure? Many thanks in advance for any advice.
I'd put a shared load function on the class, that returned the newly de-serialised object.
e.g.
Public Class MyClass
...
Public shared Function Load(ByVal filename As String) as MyClass
Dim reader = New Xml.Serialization.XmlSerializer(GetType(MyNamespace.MyClass))
Dim file = New System.IO.StreamReader(FullPath)
Return CType(reader.Deserialize(file), MyNamespace.MyClass)
End Sub
End Class
...
Dim mine as MyClass = MyClass.Load("MyObject.Xml");
Hope this helps
Alternatively,
Encapsulate the data of your class in an inner, private class.
The properties on your outer visible class delegate to the inner class.
Then Serialising and De-serialising happens on the inner class, you can then have a ctor that takes the file name, de-serialises the inner hidden object, and assigns it to the classes data store.
The "New" method in VB.Net is a constructor for the class. You can't call it for an existing instance, as the whole purpose of the method is to create new instances; it's just not how the language works. Try naming the method something like "ReadFrom" or "LoadFrom" instead.
Additionally, given those methods, I would try to implement them using a Factory Pattern. The ReadFrom method would be marked Shared and return the new instance. I would also make the method more generic. My main ReadFrom() method would accept an open textreader or xmlreader or even just a stream, rather than a file name. I would then have overloads that converts a file name into a stream for reading and calls the main method.
Of course, that assumes I use that pattern in the first place. .Net already has great support for xml serialization built into the platform. Look into the System.Xml.Serialization.XmlSerializer class and associated features.