Create Custom Class Dynamically - vb.net

I am working on a project where I need to create a multitude of custom classes to interact properly with an API (While I know there might be questions on why, and such, but the short is it has to be this way).
Is there a way to create a complete custom class dynamically on the fly? So instead of
class person
Private _Height
Property Height As Integer
Get
Return _Height
End Get
Set(value As Integer)
_Height = value
End Set
End Property
'Continue for all properties of person
I would like to be able to create a new object and through other input create this dynamically.
dim NewClass as object
dim NewProperty as property
NewProperty.name="Height"
NewProperty.datatype=string
NewClass.AddProperty(NewProperty)
Is this possible? It would save me a lot of time if it is.

I don't like late binding but there are options (I like my option strict on). Like using the DynamicObject or the ExpandoObject class. Your question is vague so I have no idea if it can work.
Sub Main()
Dim test As Object = New SampleDynamicClass()
test.SomeProperty = "123"
Console.WriteLine(test.SomeProperty)
Console.ReadLine()
End Sub
Public Class SampleDynamicClass
Inherits DynamicObject
Private _values As New Dictionary(Of String, String)
Public Sub New()
End Sub
Public Function GetPropertyValue(ByVal propertyName As String) As String
Return _values(propertyName)
End Function
Public Function SetPropertyValue(ByVal propertyName As String, ByVal value As Object) As Boolean
If _values.ContainsKey(propertyName) Then
_values(propertyName) = value.ToString()
Else
_values.Add(propertyName, value.ToString())
End If
Return True
End Function
Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder,
ByRef result As Object) As Boolean
result = GetPropertyValue(binder.Name)
Return If(result Is Nothing, False, True)
End Function
Public Overrides Function TryInvokeMember(ByVal binder As InvokeMemberBinder,
ByVal args() As Object,
ByRef result As Object) As Boolean
result = GetPropertyValue(binder.Name)
Return If(result Is Nothing, False, True)
End Function
Public Overrides Function TrySetMember(binder As SetMemberBinder, value As Object) As Boolean
Return SetPropertyValue(binder.Name, value)
End Function

Dim person = New With {Key .Height = 12}
Dim personTypes = New With {Key .Happy = 1, .Sad = 2}
Dim personsAndTypes = New With {Key .Person = person, .Type = personTypes}
The question is kind of vague, but if you have no need for other fields and methods, or reuse Anonymous Types

Related

Pass additional data to JsonConverter

I deserialize object where one of the properties is a foreign key (eg an identity value from a database table). During deserialization I would like to use a JsonConverter to retrieve the corresponding object from a collection.
I know how to write a use custom JsonConverters. I don't know how to pass the collection to the JsonConverter, because the converter is specified at design time (like below), but the collection obviously only exists at runtime:
<JsonConverter(GetType(JSonCustomConverter))>
Public Property SomeProperty As SomePropertyClass
So the JSonCustomConverter's ReadJson should look this:
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return False
Dim value As String = reader.Value.ToString().Trim()
retun MagicallyGetMyCollectionValue(value)
End Function
So the silly function name MagicallyGetMyCollectionValue is just a placeholder to show you where I am stuck. I don't want to access the collection through a global variable, but I don't know how to pass the collection to the ReadJson either.
I would be happy, if someone could point me in the right direction.
Edit: Let me try to give a better example.
Suppose I have the following class:
class ParentObject
<JssonConverter(GetType(JsonCustomConverter))>
Property SomeProperty As SomePropertyClass
end class
I would deserialize my json data like this:
dim result = JsonConvert.DeserializeObject(jsonData, GetType(ParentObject))
Now assume, that the json data doesn't contain the complete representation of an instance of the SomePropertyClass, but only a key value e.g. an key as string. Suppose I have a collection like this:
dim cache as Dictionary(of string, SomePropertyClass)
That cache shall contain all the instances that I need. So my JSonCustomConverter should have a ReadJson Function like this:
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return nothing
Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function
So I want the ReadJson to lookup the instance based on the key value.
How would I pass the cache-Dictionary into the ReadJson function? I could use a singelton class that contains the cache an som getInstance-method to retrieve it, but I wouldn't want to do this.
You can pass additional data to your custom JsonConverter using StreamingContext.Context via JsonSerializer.Context. Using this mechanism it becomes possible to map class instances to names in a generic manner.
First, define the following interfaces and generic converter:
Public Interface ISerializationContext
Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean
End Interface
Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface
Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()
Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()
Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function
Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function
Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class
Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter
Public Overrides Function CanConvert(objectType As Type) As Boolean
Return GetType(T) = objectType
End Function
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If
Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If
Dim name as String = Nothing
if (Not nameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If
writer.WriteValue(name)
End Sub
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If
Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If
Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If
dim tValue as T = Nothing
nameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class
Next, define the following concrete implementations:
Public Class RootObject
<JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> _
Public Property SomeProperty As SomePropertyClass
End Class
Public Class SomePropertyClass
End Class
Public Class MySerializationContext : Implements ISerializationContext
Public Function Add(value as SomePropertyClass, name as String) as SomePropertyClass
Return SomePropertyNameTable.Add(value, name)
End Function
Property SomePropertyNameTable as NameTable(Of SomePropertyClass) = New NameTable(Of SomePropertyClass)
Public Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean Implements ISerializationContext.TryGetNameTable
if (GetType(T) Is GetType(SomePropertyClass))
table = SomePropertyNameTable
return True
End If
table = Nothing
return False
End Function
End Class
Now, you can replace instances of SomePropertyClass with their names during deserialization as follows:
Dim context as MySerializationContext = New MySerializationContext()
Dim someProperty as SomePropertyClass = context.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }
Dim settings = new JsonSerializerSettings With _
{ _
.Context = New System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.All, context)
}
Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))
Notes:
ISerializationContext.TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) is generic so that object-to-name replacement can be supported for multiple types of objects simultaneously without the converters interfering with each other.
The concrete implementation need not be so generic, however. Here MySerializationContext only supports name replacement for instances of SomePropertyClass. Others could be added as needed.
As stated in Does Json.NET cache types' serialization information?, Newtonsoft recommends caching instances of DefaultContractResolver and its subtypes for best performance. Thus it may be preferable to pass additional data via StreamingContext.Context rather than via freshly allocated instances of subclasses of DefaultContractResolver.
Sample working .Net fiddle #1 here.
As an alternative, while the design above works, in my opinion it would be simpler to remove <JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> from SomeProperty and instead pass an appropriately initialized ObjectToNameConverter(Of SomePropertyClass), containing a local reference to some INameTable(Of SomePropertyClass), in JsonSerializerSettings.Converters.
Define the converter and interfaces like so. Notice that ObjectToNameConverter(Of T) now has a parameterized constructor and that ISerializationContext is no longer required:
Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface
Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()
Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()
Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function
Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function
Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class
Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter
Private Property NameTable as INameTable(Of T)
Public Sub New(nameTable as INameTable(Of T))
If nameTable Is Nothing
Throw new ArgumentNullException("nameTable")
End If
Me.NameTable = nameTable
End Sub
Public Overrides Function CanConvert(objectType As Type) As Boolean
Return GetType(T) = objectType
End Function
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)
Dim name as String = Nothing
if (Not NameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If
writer.WriteValue(name)
End Sub
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If
dim tValue as T = Nothing
NameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class
Then serialize as follows:
dim nameTable = new NameTable(Of SomePropertyClass)()
Dim someProperty as SomePropertyClass = nameTable.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }
Dim settings = new JsonSerializerSettings()
settings.Converters.Add(new ObjectToNameConverter(Of SomePropertyClass)(nameTable))
Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))
Doing things this way eliminates the dependence of static serialization methods on runtime code that is present in the first solution. Now all name replacement logic is handled in runtime in one location.
Sample fiddle #2 here.
As requested by #doom87er I will share the code, that worked for me. The solution is based on the comment by #dbc, with some changes. Please treat below code more like a conceptual code: I had to change some of the names and leave out some logik, that isn't required for this proof of concept. So there might be typos in it.
The main solution is to subclass the DefaultContractResolver and add the the cache-dictionary to that class. Something like this:
Public Class CacheContractResolver
Inherits DefaultContractResolver
Public Cache As Dictionary(of string, SomePropertyClass)
Public Sub New(preFilledCache As Dictionary(of string, SomePropertyClass)
Me.Cache = preFilledCache
End Sub
End Class
Then you pass the custom contract resolver using the JsonSerializerSettings like this:
Dim settings = New JsonSerializerSettings
settings.ContractResolver = New SupportControllerContractResolver(prefilledCache)
Dim result = JsonConvert.DeserializeObject(Of ParentObject)(jsonData, settings)
where prefilledCache is an instance of a dictionary containing the SomePropertyClass-objects.
The last step is to retrieve the cache in my JsonConverter's ReadJson function (that I attached to the SomeProperty as shown in the original post's sample code):
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim cacheResolver = TryCast(serializer.ContractResolver, CacheContractResolver)
if cacheResolver is nothing return nothing ' add some better null handling here
Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cacheResolver.Cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function
I tried it and it seems to work.
So in a nut shell:
Subclass the DefaultContractResolver and include all the additional data, that you need.
Pass an instance of you custom contract resolver with the additional data in the JsonSerializerSettings.
In your JsonConverter trycast the passed contract resolver back to your custom contract resolver and there you have your additional data.
I would be happy if you comment on any catches that I might miss, but I think this should be solution that I can live with.
Thanks for you coments and help.
Sascha

Comparing list of class by string length and text comparison

I have a List(Of Abbreviation).
The class "Abbreviation" contains the string members "Input", "Output" and "CaseSensitive".
The class is stated below.
I would like to sort this list so that the class with the "Input"
"ZZZ"
comes before
"zz"
The comparison should thus first compare by string length, then by alphetical order, and then by CaseSensitive.
How could I sort the list this way?
Public Class Abbreviation
Implements IComparable
Private _sIn As String = String.Empty
Private _sOut As String = String.Empty
Private _bCaseSensitive As Boolean = False
Public Property Input() As String
Get
Return _sIn
End Get
Set(value As String)
_sIn = value
End Set
End Property
Public Property Output() As String
Get
Return _sOut
End Get
Set(value As String)
_sOut = value
End Set
End Property
Public Property CaseSensitive() As Boolean
Get
Return _bCaseSensitive
End Get
Set(value As Boolean)
_bCaseSensitive = value
End Set
End Property
Public Sub New(ByVal uInput As String, ByVal uOutput As String, ByVal uCaseSensitive As Boolean)
_sIn = uInput
_sOut = uOutput
_bCaseSensitive = uCaseSensitive
End Sub
End Class
First, you can simplify your code by using automatic properties. The compiler writes the get, set, and backer fields (the private fields). This is the preferred way in recent versions of Visual Studio if there is no extra code in the getter, setter.
Another small detail with your Sub New. It violates encapsulation to set the private fields directly. Always go through the Public Properties. There could be code in the setter that needs to run before storing the data in the private fields. Classes like to keep their data close to the vest in their private fields.
Example of Auto Implemented Properties
Public Property Input As String
Public Property Output As String
Public Property CaseSensitive As Boolean
The sort can be done in one line of code using a Linq query.
Dim orderedList = From abrev In lstAbreviations Order By abrev.Input.Lenght Descending Select abrev
To check the output...
For Each abrev As Player In orderedList
Debug.Print(abrev.Input)
Next
I think I got it, except the case sensitivity. CaseSensitive should come before CaseSensitive = False.
Public Class Abbreviation
Implements IComparable(Of Abbreviation)
Private _sIn As String = String.Empty
Private _sOut As String = String.Empty
Private _bCaseSensitive As Boolean = False
Public Function CompareTo(uOther As Abbreviation) As Integer _
Implements IComparable(Of Abbreviation).CompareTo
If uOther.Input.Length > Me.Input.Length Then
Return 1
ElseIf uOther.Input.Length < Me.Input.Length Then
Return -1
Else
If uOther.Input > Me.Input Then
Return 1
Else
Return -1
End If
End If
End Function

How to use ConcurrentDictionary(of Integer, Class).TryUpdate?

What is the correct way to update a ConcurrentDictionary with a new value? I am trying AllWidgets.TryUpdate(id, myWidget, myWidget) and it returns false and does not update correctly in this type of scenario:
Public Class Widget
Public ID As Integer
Public Name As String
Public Sub New(ByVal id As Integer, ByVal name As String)
ID = id
Name = name
End Sub
End Class
Dim AllWidgets As New ConcurrentDictionary(Of Integer, Widget)
AllWidgets.TryAdd(1, New Widget(1000, "Widget A"))
AllWidgets.TryAdd(2, New Widget(1001, "Widget B"))
Dim UpdateWidget As New Widget(1001, "Widget BB")
Dim IsUpdated As Boolean = AllWidgets.TryUpdate(2, UpdateWidget, UpdateWidget)
IsUpdated is False
I guess I really don't understand how the third parameter is supposed to work for complex objects.
You'll never get True this way. The first thing you have to do is make Widgets comparable, override GetHashCode() and Equals(). Like this:
Public Class Widget
''...
Public Overrides Function GetHashCode() As Integer
Return Me.ID.GetHashCode() Xor Me.Name.GetHashCode()
End Function
Public Overrides Function Equals(obj As Object) As Boolean
Dim w = CType(obj, Widget)
Return w.ID = Me.ID AndAlso w.Name = Me.Name
End Function
End Class
Now ConcurrentDictionary can compare widgets. You'll get a True return this way:
Dim UpdateWidget As New Widget(1001, "Widget BB")
Dim OldWidget As New Widget(1001, "Widget B")
Dim IsUpdated As Boolean = AllWidgets.TryUpdate(2, UpdateWidget, OldWidget)
Debug.Assert(IsUpdated) '' fine

Casting substrings with linq into a list of object and than sorting it base on property in vb.net

This have to be in vb.net linq, i'm pretty sure I could do it in c#, but I cant find any good enough translator to help me ... even the answers I find here in SO seems to only be written in linq, hence the question which might be a duplicate of a c# one.
That being said, considering these 2 classes :
Public class User
Public Property Name() As String
Public Property Teams As TeamList
Public sub New(d as string, results as TeamList)
me.name = d
me.Teams = results
end sub
end class
Public Class TeamList
Public Property TeamName() As String
Public Property fullscore() As list(of object)
Public sub New(name as string, value as list(of string))
me.TeamName = name
me.fullscore = value
me.fullscore = getFullScore(value) (return a list of object)
end sub
End Class
I'm struggling in the final steps of my linq -to - object : (you can copy /paste this in linqpad)
Sub Main
dim Definition as new Dictionary(of String, object)
definition.add("user1_redTeam-02", new object)
definition.add("user1_redTeam-01", new object)
definition.add("user1_blueTeam-03", new object)
definition.add("user2_redTeam-01", new object)
definition.add("user1_redTeam-03", new object)
definition.add("user1_blueTeam-01",new object)
definition.add("user2_blueTeam-01", new object)
definition.add("user1_blueTeam-02", new object)
definition.add("user2_redTeam-02", new object)
Dim q3 = (From userlists In Definition.Keys.GroupBy(Function(s) s.Split("_")(0)) _
Select New With _
{.UserName = userlists.Key, _
.animationList = (From scList In userlists.GroupBy(Of String)(Function(s) s.Split("-")(0)) _
Select New With {.Team = scList.Key, _
.Score = scList.ToList()})})
q3.dump()
End Sub
this is the result :
now, all I want is to sort the .score attribute (just a simple .sort(), and instead of returning an anonymous q3 object, which I,m cluless to transform, I'd like the q3 to be a list(of User)
it think it should looks like this ... but I cant make it works, i always gets some linq conversion errors :
Unable to cast object of type 'WhereSelectEnumerableIterator2[System.Linq.IGrouping2[System.String,System.String],UserQuery+User]' to type 'System.Collections.Generic.List`1[UserQuery+User]'.
Dim q3 as List(of User)= (From userlists In Definition.Keys.GroupBy(Function(s) s.Split("_")(0)) _
Select New User(userlists.Key, (From scList In userlists.GroupBy(Of String)(Function(s) s.Split("-")(0)) _
Select New TeamList(scList.Key, scList.ToList()))))
Your code examples seem to be incorrect - for example, it seems like User.Teams should be a list of some type, not a TeamList object, which isn't really a list. Anyway, with a little modification, this is what I came up with - maybe it's close to what you were looking for (a list of users with the scores sorted). You can paste into LINQPad to run it.
Sub Main
Dim Definition As New Dictionary(of String, Object)
definition.add("user1_redTeam-02", New Object)
definition.add("user1_redTeam-01", New Object)
definition.add("user1_blueTeam-03", New Object)
definition.add("user2_redTeam-01", New Object)
definition.add("user1_redTeam-03", New Object)
definition.add("user1_blueTeam-01",New Object)
definition.add("user2_blueTeam-01", New Object)
definition.add("user1_blueTeam-02", New Object)
definition.add("user2_redTeam-02", New Object)
Dim q3 = (
From userlists In Definition.Keys.GroupBy(Function(s) s.Split("_"c)(0))
Select New User(
userlists.Key,
(From scList In userlists.GroupBy(Function(s) s.Split("-"c)(0))
Select New Team(scList.Key.Split("_"c)(1), scList.OrderBy(Function(s) s).ToList())).ToList()
)
).ToList()
q3.dump()
End Sub
' Define other methods and classes here
Public class User
Public Property Name() As String
Public Property Teams() As List(Of Team)
Public Sub New(d As String, results As List(Of Team))
Me.Name = d
Me.Teams = results
End Sub
End Class
Public Class Team
Public Property TeamName() As String
Public Property FullScore() As List(Of String)
Public Sub New(name As String, value As List(Of String))
Me.TeamName = name
Me.FullScore = value
End Sub
End Class

Pass property to access using .NET

I'm fairly sure this is possible, but what I want to do is have a generic method where I can pass in an object along with a Expression that will tell the method which Property to use in it's logic.
Can anyone get me started on the syntax for something like this?
Essentially what I would like to code is something like:
Dim firstNameMapper as IColumnMapper = new ColumnMapper(of Author)(Function(x) x.FirstName)
Dim someAuthorObject as new Author()
fistNameMapper.Map("Richard", someAuthorObject)
Now the mapper object would know to set the FirstName property to "Richard".
Now using a Function here won't work, I know this... I'm just trying to give an idea what I'm trying to work towards.
Thanks for any help!
You could use expression trees to implement this behavior, but it would be a lot simpler to pass the ColumnMapper a slightly different function. Instead of using expression that reads the property, you could give it a function that sets the value of the property:
Dim firstNameMapper as IColumnMapper = _
new ColumnMapper(of Author)(Sub(x, newValue) _
x.FirstName = newValue _
End Sub)
I think this syntax is new in Visual Studio 2010 (but I'm not a VB expert). Anyway, the type of the parameter would be Action<Author, string> and you could simply invoke it anytime you needed from the ColumnMapper to set the property.
Using expression trees, you'd have to construct expression that sets the property and compile it at runtime, so I think the additional few bits of code above are easier way to solve the problem.
Okay, so I have implemented an analogous solution (i'm not using 2010 so I can't use Tomas' solution directly) but although it compiles, the property does not seem to be set. So here are all the pieces:
Module Module1
Sub Main()
Dim inputSource() As String = {"Richard", "Dawkins"}
Dim firstNameMapper As New ColumnMapper(Of Author)(Function(obj, value) obj.FirstName = value, 0)
Dim lastNameMapper As New ColumnMapper(Of Author)(Function(obj, value) obj.LastName = value, 1)
Dim theAuthor As New Author
firstNameMapper.map(inputSource, theAuthor)
lastNameMapper.map(inputSource, theAuthor)
System.Console.WriteLine(theAuthor.FirstName + " " + theAuthor.LastName)
System.Console.ReadLine()
End Sub
End Module
Public Class ColumnMapper(Of T As {Class})
Dim _propertyMapper As Action(Of T, String)
Dim _columnIndex As Int32
Public Sub New(ByVal mapAction As Action(Of T, String), ByVal columnNumber As Int32)
_propertyMapper = mapAction
_columnIndex = columnNumber
End Sub
Public Sub map(ByVal sourceFields As String(), ByRef destinationObject As T)
_propertyMapper(destinationObject, sourceFields(_columnIndex))
End Sub
End Class
Public Class Author
Private _firstName As String
Private _lastName As String
Public Property FirstName() As String
Get
Return _firstName
End Get
Set (ByVal value As String)
_firstName = value
End Set
End Property
Public Property LastName() As String
Get
Return _lastName
End Get
Set (ByVal value As String)
_lastName = value
End Set
End Property
End Class
Any idea why the property is not being set?
Not sure why the solution using inline 'Function' doesn't work. Perhaps someone more versed in the inner workings of vb.net can explain it, but if you implement the main module as below, it works. Thanks Tomas for pointing me in the right direction!
Module Module1
Sub Main()
Dim mapAction As Action(Of Author, String)
Dim inputSource() As String = {"Richard", "Dawkins"}
Dim firstNameMapper As New ColumnMapper(Of Author)(AddressOf setFirstName, 0)
Dim lastNameMapper As New ColumnMapper(Of Author)(AddressOf setLastName, 1)
Dim theAuthor As New Author
firstNameMapper.map(inputSource, theAuthor)
lastNameMapper.map(inputSource, theAuthor)
System.Console.WriteLine(theAuthor.FirstName + " " + theAuthor.LastName)
System.Console.ReadLine()
End Sub
Public Sub setFirstName(ByVal obj As Author, ByVal value As String)
obj.FirstName = value
End Sub
Public Sub setLastName(ByVal obj As Author, ByVal value As String)
obj.LastName = value
End Sub
End Module