While CommitReader.Read()
Yield New Commit() With {
.FirstValue = CommitReader.GetInt32(CommitReader.GetOrdinal("FirstValue")),
.SecondValue = CommitReader.GetString(CommitReader.GetOrdinal("SecondValue")).Trim(),
'Lots of values
End While
I know I can do something like this; however there are 24 properties and I would like to make this part as clean as possible
While CommitReader.Read()
new Commit (){
Dim index As Integer = reader.GetOrdinal("FirstValue")
If reader.IsDBNull(index) Then
FirstValue = String.Empty
Else
FirstValue = reader(index)
End If
index = reader.GetOrdinal("SecondValue")
If reader.IsDBNull(index) Then
SecondValue = String.Empty
Else
SecondValue = reader(index)
End If
}
End While
Is there a better way to handle this type of thing? I am mainly a C# developer so if the syntax is off a little sorry, I am winging it in VB.
It's a shame that SqlDataReader doesn't have the generic Field extension method like DataRow does, but you could define your own extension method (has to be in a module in VB.NET) to help with the null checks, perhaps something like this:
<Extension>
Function GetValue(Of T)(rdr As SqlDataReader, i As Integer) As T
If rdr.IsDBNull(i) Then
Return Nothing
End If
Return DirectCast(rdr.GetValue(i), T)
End Function
And use it something like this:
While CommitReader.Read()
Yield New Commit() With {
.FirstValue = CommitReader.GetValue(Of Integer?)(CommitReader.GetOrdinal("FirstValue")),
.SecondValue = CommitReader.GetValue(Of String)(CommitReader.GetOrdinal("SecondValue")),
'Lots of values
End While
I haven't tested this fully to make sure it handles all data types appropriately (may be worth looking at DataRowExtensions.Field to see how it does it).
Note that you are using String.Empty as the "null" value for strings, while this will use Nothing/null (I also had to remove the .Trim call to avoid NREs). If you want empty string instead, you could use (adding the Trim back in):
.SecondValue = If(CommitReader.GetValue(Of String)(CommitReader.GetOrdinal("SecondValue")), String.Empty).Trim()
You may also want to move the GetOrdinal calls out of the loop to improve performance.
Obviously you have repetition in your code if ... else ... condition.
So you can extract it in another method.
For your case generic extension method seems good candidate.
Public Module Extensions
<Extension>
Public Function GetValueOrDefault(Of T)(originalValue As object,
defaultValue As T) As T
If originalValue = DbNull.Value Then
Return defaultValue
End If
return DirectCast(originalValue, T)
End Function
End Module
Then use it:
While CommitReader.Read() = True
Dim temp = new Commit With
{
Dim index As Integer = reader.GetOrdinal("FirstValue")
FirstValue = reader(index).GetValueOrDefault(String.Empty)
Dim index As Integer = reader.GetOrdinal("SecondValue")
FirstValue = reader(index).GetValueOrDefault(String.Empty)
}
End While
You can create another overload which return "default" value for given type if it is DbNull
<Extension>
Public Function GetValueOrDefault(Of T)(originalValue As object) As T
Return originalValue.GetValueOrDefault(Nothing)
End Function
Nothing in vb.net is default value, for reference types it is null for Integer it is 0 for example.
For using this overload you need provide type parameter explicitly
While CommitReader.Read() = True
Dim temp = new Commit With
{
Dim index As Integer = reader.GetOrdinal("FirstValue")
FirstValue = reader(index).GetValueOrDefault(Of String)()
Dim index As Integer = reader.GetOrdinal("SecondValue")
FirstValue = reader(index).GetValueOrDefault(Of String)()
}
End While
Notice that your solution executing reader twice, for checking is it null and for reading value. This can cause "tiny" performance issue.
So in extension method above we read value only once and then check value for DbNull.
If you concatenate a string with a Null you get the string:
FirstValue = reader(index) & ""
Kind of "unprofessional" but saves a lot of coding time if all you are doing is converting a possible Null to an empty string. Easy to forget however, so later data dependent errors may pop up.
Related
I would like to to copy that values of an array into a Structure.
Example:
' The Array
Dim Columns(2) As String
' The Structure
Private Structure Fields
Public FName As String
Public LName As String
Public Email As String
End Structure
' I would like to map it like so:
Fields.FName = Columns(0)
Fields.LName = Columns(1)
Fields.Email = Columns(2)
Obviously I could write a function if it was so simple, but really there are over 25 columns and it's a pain to write a function that would map it.
Is there some way to do this?
There really is no simple way that will work in all cases. What you are complaining is too much effort is the only way to guarantee that it will work in all cases.
That said, if you can guarantee that the number of elements in the array matches the number of properties/fields in the structure/class and that they are in the same order and of the same types then you could use Reflection in a loop, e.g.
Private Function Map(source As Object()) As SomeType
Dim result As New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return result
End Function
EDIT:
The code I have provided works as is if SomeType is a class but, as I missed the first time around, not for a structure. The reason is that structures are value types and therefore a copy of the original object is being sent to SetValue, so the field value never gets set on that original object. In theory, to prevent a copy being created, you should be able to simply box the value, i.e. wrap it in an Object reference:
Private Function Map(source As Object()) As SomeType
Dim result As Object = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function
As it turns out though, the VB compiler treats that a little differently than the C# compiler treats the equivalent C# code and it still doesn't work. That's because, in VB, the boxed value gets unboxed before being passed to the method, so a copy is still created. In order to make it work in VB, you need to use a ValueType reference instead of Object:
Private Function Map(source As Object()) As SomeType
Dim result As ValueType = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function
Please see the code below:
Public Function Test()
Dim o As Object = getVariable("Integer")
If TypeOf o Is Integer Then
'Do some processing on the integer
ElseIf TypeOf o Is Decimal Then
'Do some processing on the integer
End If
End Function
Public Function getVariable(ByVal strDataType As String)
If strDataType = "Integer" Then
Return New Integer
ElseIf strDataType = "Decimal" Then
Return New Decimal
ElseIf strDataType = "Double" Then
Return New Double
End If
End Function
I suspect there is an easier way (fewer lines of code) of doing this with Reflection?
You can use Type.GetType together with Activator.CreateInstance:
Public Function getVariable(ByVal strDataType As String)
Return Activator.CreateInstance(Type.GetType(strDataType))
End Function
For strDataType you need to be using System.Int32, System.Decimal and System.Double respectively. If you want to keep it as Integer etc., you need to incorporate string translation, for example, have a Dictionary(Of String, String), with entries like ("Integer", "System.Int32").
The real question is what ars you trying to solve? Activator will work. Are trying to create a factory or an IOC container. Could you provide more detail? You could also create a dictionary were the key is of type string and then item stores delegates used to actual create the type.
The obvious first thought is:
Public Function CheckStrings(ByVal input As String()) As Boolean
For Each s As String In input
If s.Length > 0 Then Return True
Next
Return False
End Function
I'm sure there is a simpler way than that though. At least simpler in terms of the code if not necessarily the performance.
End result:
Well, you guys did a pretty good job of simplifying. Well done.
I think I'll still use an extension to make it just that much simpler in the main code. The final result really isn't too bad by itself though.
Here's my final code:
<Extension()> _
Public Function AnyNonZero(ByVal value As String()) As Boolean
If Not value.All(Function(x) String.IsNullOrEmpty(x)) Then Return True
Return False
End Function
You can use this to return true if all elements are zero length.
Dim inputIsEmpty As Boolean = Array.TrueForAll(input, Function(x) x.Length = 0)
Be careful of null references. You may want to use this instead:
Dim inputIsEmpty As Boolean = Array.TrueForAll(input, Function(x) String.IsNullOrEmpty(x))
How about this? It uses your string array named input.
Array.TrueForAll(input, AddressOf ZeroLengthString)
Private Function ZeroLengthString(ByVal s As String) As Boolean
Return s.Length = 0
End Function
Here's a Linq function similar to Array.TrueForAll:
Dim allEmpty = values.All(Function(x) x.Length = 0)
I find it to be a bit more easily readable than Array.TrueForAll.
Having a bit of trouble using the List.Find with a custom predicate
i have a function that does this
private function test ()
Dim test As Integer = keys.Find(AddressOf FindByOldKeyAndName).NewKey
here's the function for the predicate
Private Shared Function FindByOldKeyAndName(ByVal k As KeyObj) As Boolean
If k.OldKey = currentKey.OldKey And k.KeyName = currentKey.KeyName Then
Return True
Else
Return False
End If
End Function
by doing it this way means i have to have a shared "currentKey" object in the class, and i know there has to be a way to pass in the values i'm interested in of CurrentKey (namely, keyname, and oldkey)
ideally i'd like to call it by something like
keys.Find(AddressOf FindByOldKeyAndName(Name,OldVal))
however when i do this i get compiler errors.
How do i call this method and pass in the values?
You can cleanly solve this with a lambda expression, available in VS2008 and up. A silly example:
Sub Main()
Dim lst As New List(Of Integer)
lst.Add(1)
lst.Add(2)
Dim toFind = 2
Dim found = lst.Find(Function(value As Integer) value = toFind)
Console.WriteLine(found)
Console.ReadLine()
End Sub
For earlier versions you'll have to make "currentKey" a private field of your class. Check my code in this thread for a cleaner solution.
I have an object that manages a list of Unique Property Types.
Example:
obj.AddProperty(new PropertyClass(PropertyTypeEnum.Location,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
//throws exception because property of type CallingCard already exists
Here is some code to check if properties already exist
Public Sub AddProperty(ByVal prop As PropertyClass)
If Properties.Count < 50 Then
'Lets verify this property does not exist
Dim existingProperty As PropertyClass = _
Properties.Find(Function(value As PropertyClass)
Return value.PropertyType = prop.PropertyType
End Function)
'if it does not exist, add it otherwise throw exception
If existingProperty Is Nothing Then
Properties.Add(prop)
Else
Throw New DuplicatePropertyException("Duplicate Property: " + _
prop.PropertyType.ToString())
End If
End If
End Sub
I haven't needed to try this in newer versions of VB.Net which might have a nicer way, but in older versions the only way that I know of would be to have a shared member in your class to set with the value before the call.
There's various samples on the net of people creating small utility classes to wrap this up to make it a little nicer.
I've found a blog with a better "real world" context example, with good variable names.
The key bit of code to Find the object in the list is this:
' Instantiate a List(Of Invoice).
Dim invoiceList As New List(Of Invoice)
' Add some invoices to List(Of Invoice).
invoiceList.Add(New Invoice(1, DateTime.Now, 22))
invoiceList.Add(New Invoice(2, DateTime.Now.AddDays(10), 24))
invoiceList.Add(New Invoice(3, DateTime.Now.AddDays(30), 22))
invoiceList.Add(New Invoice(4, DateTime.Now.AddDays(60), 36))
' Use a Predicate(Of T) to find an invoice by its invoice number.
Dim invoiceNumber As Integer = 1
Dim foundInvoice = invoiceList.Find(Function(invoice) invoice.InvoiceNumber = invoiceNumber)
For more examples, including a date search, refer to Mike McIntyre's Blog Post
What I want to do is, based on the type of T do different opperations. Below is a simple example of my problem.
Public Shared Function Example(Of T)() As T
Dim retval As T
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
ElseIf TypeOf retval Is Integer Then
Dim myInt As Integer = 101
retval = myInt
End If
Return retval
End Function
I get the error "Value of Type 'String' Cannot be converted to 'T'" Same with the integer part. If I cast either to an object before asigning them to retval it works but I think that would defeat my purpose and be less efficient. Any Ideas? Thanks!
It's probably a bit late, but try this:
Public Shared Function CAnyType(Of T)(ByRef UTO As Object) As T
Return CType(UTO, T)
End Function
Public Shared Function ExecuteSQLstmtScalar(Of T)(ByVal strSQL As String) As T
Dim T_ReturnValue As T
' Here we have the result of a DB query '
Dim obj As Object = "Value from DB query cmd.ExecuteScalar"
Dim strReturnValue As Object = obj.ToString();
Try
Dim tReturnType As Type = GetType(T)
If tReturnType Is GetType(String) Then
Return CAnyType(Of T)(strReturnValue)
ElseIf tReturnType Is GetType(Boolean) Then
Dim bReturnValue As Boolean = Boolean.Parse(strReturnValue)
Return CAnyType(Of T)(bReturnValue)
ElseIf tReturnType Is GetType(Integer) Then
Dim iReturnValue As Integer = Integer.Parse(strReturnValue)
Return CAnyType(Of T)(iReturnValue)
ElseIf tReturnType Is GetType(Long) Then
Dim lngReturnValue As Long = Long.Parse(strReturnValue)
Return CAnyType(Of T)(lngReturnValue)
Else
MsgBox("ExecuteSQLstmtScalar(Of T): This type is not yet defined.")
End If
Catch ex As Exception
End Try
Return Nothing
End Function
(the secrect is casting your generic result to object, then casting from type Object to template type T).
PS:
You are responsible to ensure that your code works correctly with nullable types and NOT nullable types, as well as System.DbNull.Value. For example when string is NULL and return value type is Boolean (not nullable). On a sidenote, please also note that VB Nothing is NOT equal NULL, it's equal to C#'s default(T) (e.g. System.Guid.Empty for Guid)
With a generic method, T will be of exactly one type each time. Let's say that you have code calling Example(Of Integer). Now, in your mind, replace T with Integer. The resulting method will contain these lines (amongst others).
Dim retval As Integer
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
' more code follows '
Assigning a String to an integer like that will never work. Sure, that code will also never execute, since the If-block prevents that, but the code will still not compile. (As a side not, the above code will fail to compile because the TypeOf keyword is restricted to use with reference types, but that is another story)
Typically when creating generic methods, you will want to do the same thing with whatever input you get, but in a type safe manner. If you want to have different behavior for different types of input, you are usually better off by overloading the methods instead.
retVal = (T) "Hello World!"
Do retval = Ctype(Mystring, T) or retVal = Ctype(MyInt, T)
An alternative solution is encapsulate this kind of logic in a class and use VB CallByName function:
Class Aux(Of T)
Public Value As T
Private dicc As Dictionary(Of String, Object)
Sub New()
dicc = New Dictionary(Of String, Object)
dicc.Add("system.string", "hola")
dicc.Add("system.int32", 15)
dicc.Add("system.double", 15.0)
End Sub
Public Function Test() As T
Dim typeName As String = GetType(T).ToString.ToLower
If dicc.ContainsKey(typeName) Then
CallByName(Me, "Value", CallType.Set, dicc(typeName))
End If
Return Value
End Function
Protected Overrides Sub Finalize()
MyBase.Finalize()
If Not (dicc Is Nothing) Then dicc.Clear()
dicc = Nothing
End Sub
End Class