Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable generic return type in C#

Tags:

c#

generics

Is there any way to have a method return any one of a number of generic types from a method? For example, I have the following:

public static T ParseAttributeValue<T>(this XElement element, string attribute)
    {
        if(typeof(T) == typeof(Int32))
        {
            return Int32.Parse(element.Attribute(attribute).Value);
        }

        if(typeof(T) == typeof(Double))
        {
            return Double.Parse(element.Attribute(attribute).Value);
        }

        if(typeof(T) == typeof(String))
        {
            return element.Attribute(attribute).Value;
        }

        if(typeof(T) == typeof(ItemLookupType))
        {
            return Enum.Parse(typeof(T), element.Attribute(attribute).Value);
        }
    }

(This is only a very quick mockup, I'm aware that any production code would need to be significantly more thorough in null checks etc...)

But the compiler doesn't like it, complaining that Int32 cannot be implicitly converted to T (it doesn't work with a cast either). I can understand that. At compile time it has no way to know what T is, but I'm checking it beforehand. Is there anyway I can make this work?

like image 255
richzilla Avatar asked Jul 19 '12 18:07

richzilla


3 Answers

I've done these types of generic methods in the past. The easiest way to get type inference is to provide a generic converter function.

public static T ParseAttributeValue<T>
          (this XElement element, string attribute, Func<string, T> converter)
{
  string value = element.Attribute(attribute).Value;
  if (String.IsNullOrWhiteSpace(value)) {
    return default(T);
  }

  return converter(value);
}

You can use it like the following:

int index = element.ParseAttributeValue("index", Convert.ToInt32);
double price = element.ParseAttributeValue("price", Convert.ToDouble);

You can even provide your own functions and have all the fun in the world (even return anonymous types):

ItemLookupType lookupType = element.ParseAttributeValue("lookupType",
  value => Enum.Parse(typeof(ItemLookupType), value));

var item = element.ParseAttributeValue("items",
  value => {
    List<string> items = new List<string>();
    items.AddRange(value.Split(new [] { ',' }));
    return items;
  });
like image 129
Joshua Avatar answered Oct 20 '22 20:10

Joshua


.Net already has a bunch of great string conversion routines you can use! A TypeConverter can do most of the heavy lifting for you. Then you don't have to worry providing your own parsing implementations for built-in types.

Note that there are locale-aware versions of the APIs on TypeConverter that could be used if you need to handle parsing values expressed in different cultures.

The following code will parse values using the default culture:

using System.ComponentModel;

public static T ParseAttributeValue<T>(this XElement element, string attribute)
{
    var converter = TypeDescriptor.GetConverter(typeof(T));
    if (converter.CanConvertFrom(typeof(string)))
    {
        string value = element.Attribute(attribute).Value;
        return (T)converter.ConvertFromString(value);
    }

    return default(T);
}

This will work for a lot of built-in types, and you can decorate custom types with a TypeConverterAttribute to allow them to participate in the type conversion game too. This means that in the future you will be able to parse new types without having to change the implementation of the ParseAttributeValue.

see: http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter.aspx

like image 34
Monroe Thomas Avatar answered Oct 20 '22 19:10

Monroe Thomas


Why are you using the type parameter as the return type at all? This would work, just requires a cast after calling:

public static Object ParseAttributeValue<T>(this XElement element, string attribute)
{
    if(typeof(T) == typeof(Int32))
    {
        return Int32.Parse(element.Attribute(attribute).Value);
    }

    if(typeof(T) == typeof(Double))
    {
        return Double.Parse(element.Attribute(attribute).Value);
    }

    if(typeof(T) == typeof(String))
    {
        return element.Attribute(attribute).Value;
    }

    if(typeof(T) == typeof(ItemLookupType))
    {
        return Enum.Parse(typeof(T), element.Attribute(attribute).Value);
    }
}

Or better yet:

public static Int32 ParseAsInt32(this XElement element, string attribute)
{
    return Int32.Parse(element.Attribute(attribute).Value);
}

// etc, repeat for each type

This second approach has the additional benefit of having a much higher likelihood of getting inlined, plus it will (for value types like Int32) prevent the need to box/unbox the value. Both of these will cause the method to perform somewhat faster.

like image 4
Chris Shain Avatar answered Oct 20 '22 20:10

Chris Shain