Подтвердить что ты не робот

Переменный общий тип возвращаемого значения в С#

Можно ли каким-либо способом вернуть метод из какого-либо одного из нескольких типов? Например, у меня есть следующее:

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);
        }
    }

(Это всего лишь очень быстрый макет, я знаю, что любой производственный код должен быть значительно более тщательным в нулевых проверках и т.д.)

Но компилятор ему не нравится, жалуясь, что Int32 не может быть неявно преобразован в T (он также не работает с листом). Я могу понять, что. Во время компиляции он не знает, что такое T, но я проверял его заранее. В любом случае я могу сделать эту работу?

4b9b3361

Ответ 1

Я делал эти типы общих методов в прошлом. Самый простой способ получить вывод типа - предоставить общую функцию преобразователя.

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);
}

Вы можете использовать его, как показано ниже:

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

Вы даже можете предоставить свои собственные функции и повеселиться в мире (даже вернуть анонимные типы):

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;
  });

Ответ 2

.Net уже имеет множество отличных строковых процедур преобразования, которые вы можете использовать! A TypeConverter может сделать большую часть тяжелой работы для вас. Тогда вам не придется беспокоиться, предоставляя свои собственные синтаксические разборки для встроенных типов.

Обратите внимание, что существуют версии API API на языке TypeConverter, которые могут использоваться, если вам нужно обрабатывать значения синтаксического разбора, выраженные в разных культурах.

Следующий код будет анализировать значения, используя культуру по умолчанию:

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);
}

Это будет работать для множества встроенных типов, и вы можете украсить пользовательские типы с помощью TypeConverterAttribute, чтобы они могли участвовать в игре преобразования типов. Это означает, что в будущем вы сможете анализировать новые типы без необходимости изменения реализации ParseAttributeValue.

см. ниже: http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverter.aspx

Ответ 3

Почему вы используете параметр типа как возвращаемый тип вообще? Это будет работать, просто требуется бросок после вызова:

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);
    }
}

Или еще лучше:

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

// etc, repeat for each type

Этот второй подход имеет дополнительное преимущество, заключающееся в том, что он имеет гораздо более высокую вероятность получения встроенного, плюс он (для типов значений, таких как Int32) предотвращает необходимость вставить/удалить значение. Оба они заставят метод работать несколько быстрее.

Ответ 4

Не уверен, что это именно то, что вы хотите, но вы можете сделать возврат, если вы нажмете на object сначала, затем на T

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

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

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

        return default(T);
    }

Однако вы все равно должны предоставить T во время компиляции, вызывая метод:

int value = element.ParseAttributeValue<int>("attribute");

Ответ 5

Вот два способа сделать это...

    static T ReadSetting<T>(string value)
    {
        object valueObj = null;
        if (typeof(T) == typeof(Int32))
            valueObj = Int32.Parse(value);
        return (T)valueObj;
    }
    static dynamic ReadSetting2<T>(string value)
    {
        if (typeof(T) == typeof(Int32))
            return Int32.Parse(value);
        throw new UnsupportedException("Type is unsupported");
    }
    static void Main(string[] args)
    {
        int val1 = ReadSetting<Int32>("2");
        int val2 = ReadSetting2<Int32>("3");
    }

Ответ 6

С шаблонами С++ эта работа будет работать, но только если каждая часть кода была в другой, отдельной специализации. Дело в том, что эта работа заключается в том, что неиспользуемые шаблоны функций не скомпилированы (или, точнее, не полностью созданы), поэтому тот факт, что часть кода будет недействительной, если эта копия шаблона была создана с использованием другого типа, не придумай.

С# отличается, а AFAIK не существует специализации для дженериков. Один из способов добиться того, что вы пытаетесь сделать, работая в рамках ограничений С#, - это создать одну функцию с более абстрактным возвращаемым типом и использовать ParseAttributeValue только для того, чтобы передать ее в T.

Итак, у вас будет:

private static Object AbstractParseValue(System.Type t, XElement element, string attribute)

и

public static T ParseAttributeValue<T>(this XElement element, string attribute)
{
     return (T)AbstractParseValue(typeof(T), element, attribute);
}

Ответ 7

Я бы предположил, что вместо того, чтобы тестировать параметр типа каждый раз, когда выполняется процедура, вы должны создать общий статический класс примерно так:

internal static class ElementParser<T>
{
  public static Func<XElement, string, T> Convert = InitConvert;

  T DefaultConvert(XElement element, string attribute)
  {
    return Default(T); // Or maybe throw exception, or whatever
  }

  T InitConvert(XElement element, string attribute)
  {
    if (ElementParser<int>.Convert == ElementParser<int>.InitConvert)
    {  // First time here for any type at all
      Convert = DefaultConvert; // May overwrite this assignment below
      ElementParser<int>.Convert =
        (XElement element, string attribute) =>
          Int32.Parse(element.Attribute(attribute).Value);
      ElementParser<double>.Convert =
        (XElement element, string attribute) =>
          Int32.Parse(element.Attribute(attribute).Value);
      // etc. for other types
    }
    else // We've done other types, but not this type, and we don't do anything nice for it
    {
      Convert = DefaultConvert;
    }
    return Convert(element, attribute);      
  }
}
public static T ParseAttributeValue(this XElement element, string attribute)
{
  ElementParser<T>.Convert(element, attribute);
}

Используя этот подход, нужно использовать специальную обработку только при использовании определенного типа. После этого преобразование может быть выполнено с использованием только одного общего вызова делегата. После этого можно легко добавить любое количество типов и даже позволить регистрировать конвертеры для любого желаемого типа во время выполнения.