views:

96

answers:

6

Hello world!

Using the following code:

Function GetSetting(Of T)(ByVal SettingName As String, ByRef DefaultVal As T) As T
    Return If(Configuration.ContainsKey(SettingName), CType(Configuration(SettingName), T), DefaultVal)
End Function

Yields the following error:

Value of type 'String' cannot be converted to 'T'.

Any way I could specify that in all cases, the conversion will indeed be possible (I'm basically getting integers, booleans, doubles and strings).

Edit: There seem to be three solutions now:

  • Using the `ValueAs` function provided by AMissico
  • Casting to an `object`, then to `T`, with a check for null values
  • Using a `DirectCast` over Convert.ChangeType

Which would you suggest?

Edit 2: Would this code work?

Function GetSetting(Of T)(ByVal SettingName As String, Optional ByRef DefaultVal As T = Nothing) As T
    Return If(Configuration.ContainsKey(SettingName), ConvertTo(Of T)(Configuration(SettingName)), DefaultVal)
End Function

Function ConvertTo(Of T)(ByVal Str As String) As T
    Return If(Str Is Nothing Or Str = "", Nothing, CType(CObj(Str), T))
End Function

Edit 3: [AMJ] Working Code

Function GetSetting(Of T)(ByVal SettingName As String) As T
    Return GetSetting(Of T)(SettingName, Nothing)
End Function
Function GetSetting(Of T)(ByVal SettingName As String, ByVal DefaultVal As T) As T
    Dim sValue As String = Configuration(SettingName)
    If Len(sValue) = 0 Then
        Return DefaultVal
    Else
        Return CType(CObj(sValue), T)
    End If
End Function

Quick Test Method

Public Sub DoIt()

    Me.Configuration.Add("KeyN", Nothing)
    Me.Configuration.Add("KeyE", String.Empty) '""
    Me.Configuration.Add("Key1", "99")
    Me.Configuration.Add("Key2", "1/1/2000")
    Me.Configuration.Add("Key3", "True")
    Me.Configuration.Add("Key4", "0")

    Dim o As Object 'using object in order to see what type is returned by methods

    o = Value(Of Integer)("KeyN", 10) '10
    o = Value(Of Integer)("KeyE", 10) '10
    o = Value(Of Integer)("Key1", 10) '99

    o = Value(Of Date)("KeyN", #11/11/2010#)
    o = Value(Of Date)("KeyE", #11/11/2010#)
    o = Value(Of Date)("Key2", #11/11/2010#)

    o = GetSetting(Of Integer)("KeyN", 10) '10
    o = GetSetting(Of Integer)("KeyE", 10) '10
    o = GetSetting(Of Integer)("Key1", 10) '99

    o = GetSetting(Of Date)("KeyN", #11/11/2010#)
    o = GetSetting(Of Date)("KeyE", #11/11/2010#)
    o = GetSetting(Of Date)("Key2", #11/11/2010#)

    Stop
End Sub
A: 

You need to put constraints on your method definition to constraint it to types that can be cast to string. See this for adding constraints to generics. You can only constrain to one classthough. I would constrain to object (becuase you can still specify t aas any type derived from the contraint type), then call ToString() instead of casting.

Ben Robinson
You can constrain on multiple interfaces.
Konrad Rudolph
Hmmm, but I want to do it the other way ; that is, convert a string to T, and not T to a string.
CFP
A: 

Is the type of Configuration a Dictionary<string, string> (c#)?
If yes, you don't need generics.

EDIT: If the Configuration(SettingsName) returns a string, the container is non-generic & hence you won't need generics here.

EDIT2:

void Main()
{
    Test<bool>.trythis("false");
    Test<bool>.trythis("true");
    Test<int>.trythis("28");
    Test<decimal>.trythis(decimal.MaxValue.ToString());
}

public class Test<T>
{
  public static void trythis(string value)
    {
        T retVal = (T)Convert.ChangeType(value, typeof(T));
        Console.WriteLine(retVal.GetType());
    }
}

I am sorry, I don't know vb.net enough.
It could be Convert.ChangeType(Configuration(SettingsName), T.GetType()) & then using a DirectCast over the results.

Hope this helps.

shahkalpesh
The function is specifically to convert the resulting String to a strongly-typed T.
AMissico
AMissico: Exactly ; basically, the point is to avoid conversions everywhere in the code, and to provide a cleaner approach.
CFP
+1  A: 

This requires a bit of hocus-pocus:

Public Function GetSetting(Of T As IConvertible)(ByVal SettingName As String, ByRef DefaultVal As T) As T
    Dim formatter As IFormatProvider = CultureInfo.InvariantCulture
    Dim targetType As Type = GetType(T)

    Dim value As IConvertible = Nothing
    Return If(Configuration.TryGetValue(SettingName, value), _
        DirectCast(value.ToType(targetType, formatter), T), _
        DefaultVal)
End Function

This code uses reflection to invoke the appropriate conversion method from the IConvertible interface that all basic value types implement. The result of this conversion can be cast using DirectCast.

Simplified: This code uses the ToType method, hence doesn’t require reflection.

Note that the IConvertible type constraint isn’t even strictly necessary here – since we call the IConvertible method on the String return value of the configuration, not the actual type. But the constraint is still useful since this ensures that the appropriate conversion will exist.

Konrad Rudolph
Wooo. Not exactly pretty =)
CFP
@CFP: Not at all. Nor efficient (that can be alleviated by converting the method info to a delegate and caching it). But the easiest route (that I know of).
Konrad Rudolph
@CFP: Something else: at the moment the code queries the dictionary twice, which may be inefficient. You can use `TryGetValue` instead, that only queries it once.
Konrad Rudolph
I could not get this to work.
AMissico
@AMissico: The code was untested – it was missing the `IFormatProvider` argument. But I’ve changed the code now to be a bit simpler and no longer require reflection. And now it’s tested and *works*.
Konrad Rudolph
@Konrad Rudolph: Ah, totally forgot about adding the `IFormatProvider` parameter.
AMissico
@Konrad Rudolph: `System.Convert.ChangeType` already does this. It also defaults to `CurrentCulture`. (Reference is Microsoft .NET 2.0 Soure Code for convert.cs and method's comments.)
AMissico
+2  A: 

The Value(Of T) and ValueAs methods support nullable-types. I used Microsoft .NET 2.0 source code as a reference.

This is well-tested and production ready code.

There is no error handling in these "library" functions. It is the responsibility of the caller to handle any conversion errors that occur. The only conversion errors generated are obvious errors, such as trying to convert the string "abc" to Integer.


Public Sub DoIt()
    Dim o As Object
    o = Value(Of Integer)("foo", 10)
    o = Value(Of DateTime)("xxx", #1/1/2000#)
    o = Value(Of Boolean?)("nop", True)
    Stop
End Sub

Public Function GatherTag(ByVal tag As String) As String
    If tag = "foo" Then
        Return "99"
    Else
        Return String.Empty
    End If
End Function

''' <summary>
''' Provides strongly-typed access to the tag values. The method also supports nullable types.
''' </summary>
''' <typeparam name="T">A generic parameter that specifies the return type.</typeparam>
''' <param name="tag">The ExifTool Tag Name,</param>
''' <returns>The value, of type T, of the tag.</returns>
Public Function Value(Of T)(ByVal tag As String, ByVal defaultValue As T) As T
    Return DirectCast(ValueAs(GetType(T), tag, defaultValue), T)
End Function

''' <summary>
''' Returns the tag's value as the specified type. The method also supports nullable types.
''' </summary>
''' <param name="type">The type to return the tag value as.</param>
''' <param name="tag">The ExifTool Tag Name,</param>
''' <returns>The value of the tag as the type requested.</returns>
Public Function ValueAs(ByVal type As System.Type, ByVal tag As String, ByVal defaultValue As Object) As Object
    Dim oResult As Object = Nothing

    Dim oTag As String = GatherTag(tag)

    If Len(oTag) = 0 Then

        'use specified default value

        oResult = defaultValue

    Else

        'is requested type a generic type?

        If type.IsGenericType AndAlso type.GetGenericTypeDefinition Is GetType(Nullable(Of )) Then

            Dim oUnderlyingType As Type = Nullable.GetUnderlyingType(type)

            Dim oConstructed As Type = type.GetGenericTypeDefinition.MakeGenericType(oUnderlyingType)

            Dim oValue As Object

            oValue = System.Convert.ChangeType(oTag, oUnderlyingType)

            If oValue IsNot Nothing Then
                oResult = Activator.CreateInstance(oConstructed, oValue)
            End If

        Else

            'non-generic type

            oResult = System.Convert.ChangeType(oTag, type)

        End If

    End If

    Return oResult
End Function
AMissico
Nice, clean, and understandable.
Fred F.
Thanks! Edited my question to ask which solution was the best.
CFP
I don’t understand the meaning of your `tag` variable. That seems library-specific and not appropriate in this context. Did you pluck the code from some application? However, nice handling of nullable types.
Konrad Rudolph
@Konrad Rudolph: Thanks. I was inspired by the XPathNavigator's `ValueAs...` methods. Handling nullable-types was a challenge to get right. I wanted **full-proof** methods, so it took two days to 1) understand how nullable-types are created, 2) understand how nullable-types are passed and returned from methods, 3) understand how to debug nullable-types (the hardest part), 4) using test databases to test methods against 52MB of XML Data (110,000? values), and lastly, 5) optimization for fastest possible execution due to the potential heavy and critical use of each method.
AMissico
@Konrad Rudolph: I grabbed the code from an application that wraps "ExifTool". I wanted to post this code because of the nullable support. In "WinForms", every control has a `Tag` property which is "user-defined data associated with the object." I occasionally give my library classes a "tag" property. "ExifTool" uses the term `tag` for "meta-data properties". The two happened to clash in this instance; thus, the confusion. I left as "tag" because I couldn't think or find a better descriptive name.
AMissico
The suggested implementation of these three methods is to add them to a class that has a `Tag` property, where the class overrides the implementation details of `GatherTag`. The basic implementation of `GatherTag` would be `Return DirectCast(Me.Tag, String)`. Note that I use the term "gather" instead of "get" when a method must "construct" or gather the return value.
AMissico
Ah, it just hit me => Tag is Key. How obvious. :O)
AMissico
A: 
Public Function Value(Of T)(ByVal SettingName As String, ByVal DefaultValue As T) As T
    Return If(Configuration.ContainsKey(SettingName), DirectCast(System.Convert.ChangeType(Configuration(SettingName), GetType(T)), T), DefaultValue)
End Function
Fred F.
Fails when `Configuration(SettingName)` returns `String.Empty` ("") or `Nothing`.
AMissico
A: 

Easy, just make it forget that it has a string by casting it to an object first.

Function GetSetting(Of T)(ByVal SettingName As String, ByRef DefaultVal As T) As T
    Return If(Configuration.ContainsKey(SettingName), CType(CObj(Configuration(SettingName)), T), DefaultVal)
End Function
Jonathan Allen
Fails when `Configuration(SettingName)` returns `String.Empty` ("").
AMissico
Then change the if statement to be (Configuration.ContainsKey(SettingName) ANDALSO Configuration(SettingName) <> "")
Jonathan Allen