Создание универсального метода на C #


86

Я пытаюсь объединить несколько похожих методов в общий метод. У меня есть несколько методов, которые возвращают значение строки запроса или null, если эта строка запроса не существует или имеет неправильный формат. Это было бы достаточно просто, если бы все типы изначально допускали значение NULL, но я должен использовать универсальный тип, допускающий значение NULL, для целых чисел и дат.

Вот что у меня есть сейчас. Однако он вернет 0, если числовое значение недействительно, и, к сожалению, это допустимое значение в моих сценариях. Кто-нибудь может мне помочь? Благодаря!

public static T GetQueryString<T>(string key) where T : IConvertible
{
    T result = default(T);

    if (String.IsNullOrEmpty(HttpContext.Current.Request.QueryString[key]) == false)
    {
        string value = HttpContext.Current.Request.QueryString[key];

        try
        {
            result = (T)Convert.ChangeType(value, typeof(T));  
        }
        catch
        {
            //Could not convert.  Pass back default value...
            result = default(T);
        }
    }

    return result;
}

Он портирует кучу реализаций, поэтому вызовите старую функциональность, запомните результат, вызовите новую функциональность, запомните результат, сравните. Теперь проделайте это 100 раз с кучей случайных входов и вуаля!
Hamish Grubijan

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

Глядя на ответы, я немного сбит с толку: параметрируют ли ваши абоненты с помощью int или int? как Т?

Мне кажется, что вместо того, чтобы обрабатывать это внутренне, вы должны позволить методу генерировать исключение. Возможно, это только я, но кто-то может быть сбит с толку, почему их вызов всегда возвращает значение по умолчанию, поскольку они не видят исключения, которое генерируется при ChangeTypeсбое.
Crush

Ответы:


65

Что, если вы указали значение по умолчанию для возврата вместо использования по умолчанию (T)?

public static T GetQueryString<T>(string key, T defaultValue) {...}

Это также упрощает вызов:

var intValue = GetQueryString("intParm", Int32.MinValue);
var strValue = GetQueryString("strParm", "");
var dtmValue = GetQueryString("dtmPatm", DateTime.Now); // eg use today's date if not specified

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


Да, это кажется более жизнеспособным, чем полагаться на значение по умолчанию целого числа. Я буду иметь это в виду. Я все еще надеюсь, что моя исходная функция будет работать для всех типов, хотя я могу просто прибегнуть к использованию неуниверсальных функций.
Майк Коул

Почему бы просто не вернуть что-то отличное от нуля для недопустимого целого числа? Вы можете вернуть все, что хотите, что не является допустимым значением или уже имеет особую цель, например null. Вы даже можете создать собственный тип с именем InvalidInteger или что-то в этом роде. Вы возвращаете null для неверной строки запроса, верно? Вы также можете вернуть это значение для недопустимого целого числа, поэтому null будет означать просто «что-то плохое, и у меня нет ценности для вас», и, возможно, передать код причины по ссылке на функцию?
Дэн Чарпстер

1
Как получить значение для: long ? testгде по умолчанию должно быть null
Аршад

16

Знаю, знаю, но ...

public static bool TryGetQueryString<T>(string key, out T queryString)

4
Try-Pattern должен быть хорошо известен любому разработчику .NET. Это неплохо, если вы спросите меня. В F # или NET 4.0 вы должны использовать Option (или Choice)
Кристиан Клаузер

6
Если по какой-либо другой причине, я стараюсь избегать этого, потому что мне не нравится необходимость «заранее объявлять» эту выходную переменную, особенно если она никогда не используется - пустая трата того, что в противном случае могло бы быть совершенно хорошей строкой кода.
Джей

На самом деле это самый простой способ решить вашу проблему - определить одну функцию, как указано выше, + два помощника, которые будут использовать эту функцию (это будут 4 вкладыша).
greenoldman

1
Я ненавижу шаблон «Попробовать» по той же причине, что и заявил Джей. Я бы предпочел одну общую функцию, если это возможно, что и было моей первоначальной целью.
Майк Коул

11
Делать или не делать, нет попытки! <Yoda>
Rabbit

12

Что насчет этого? Измените тип возврата с TнаNullable<T>

public static Nullable<T> GetQueryString<T>(string key) where T : struct, IConvertible
        {
            T result = default(T);

            if (String.IsNullOrEmpty(HttpContext.Current.Request.QueryString[key]) == false)
            {
                string value = HttpContext.Current.Request.QueryString[key];

                try
                {
                    result = (T)Convert.ChangeType(value, typeof(T));  
                }
                catch
                {
                    //Could not convert.  Pass back default value...
                    result = default(T);
                }
            }

            return result;
        }

Ошибка: тип «T» должен быть типом значения, не допускающим значения NULL, чтобы использовать его в качестве параметра «T» в универсальном типе или методе «System.Nullable <T>».
Майк Коул,

Также нужно указать where T : struct.
Aaronaught

@Mike C: Вы не должны получать такую ​​же ошибку. Отредактированный код определенно компилируется.
Aaronaught

Ага, теперь понял. Итак, что происходит, когда я хочу вызвать это для типа String? Он не примет это как сейчас.
Майк Коул

@MikeC, не думайте, что это возможно, потому что stringэто nullableценность
Graviton

5

Вы можете использовать своего рода монаду Maybe (хотя я бы предпочел ответ Джея)

public class Maybe<T>
{
    private readonly T _value;

    public Maybe(T value)
    {
        _value = value;
        IsNothing = false;
    }

    public Maybe()
    {
        IsNothing = true;
    }

    public bool IsNothing { get; private set; }

    public T Value
    {
        get
        {
            if (IsNothing)
            {
                throw new InvalidOperationException("Value doesn't exist");
            }
            return _value;
        }
    }

    public override bool Equals(object other)
    {
        if (IsNothing)
        {
            return (other == null);
        }
        if (other == null)
        {
            return false;
        }
        return _value.Equals(other);
    }

    public override int GetHashCode()
    {
        if (IsNothing)
        {
            return 0;
        }
        return _value.GetHashCode();
    }

    public override string ToString()
    {
        if (IsNothing)
        {
            return "";
        }
        return _value.ToString();
    }

    public static implicit operator Maybe<T>(T value)
    {
        return new Maybe<T>(value);
    }

    public static explicit operator T(Maybe<T> value)
    {
        return value.Value;
    }
}

Ваш метод будет выглядеть так:

    public static Maybe<T> GetQueryString<T>(string key) where T : IConvertible
    {
        if (String.IsNullOrEmpty(HttpContext.Current.Request.QueryString[key]) == false)
        {
            string value = HttpContext.Current.Request.QueryString[key];

            try
            {
                return (T)Convert.ChangeType(value, typeof(T));
            }
            catch
            {
                //Could not convert.  Pass back default value...
                return new Maybe<T>();
            }
        }

        return new Maybe<T>();
    }

4

Convert.ChangeType()некорректно обрабатывает типы или перечисления, допускающие значение NULL, в .NET 2.0 BCL (хотя я думаю, что это исправлено для BCL 4.0). Вместо того, чтобы усложнять внешнюю реализацию, заставьте конвертер выполнять больше работы за вас. Вот реализация, которую я использую:

public static class Converter
{
  public static T ConvertTo<T>(object value)
  {
    return ConvertTo(value, default(T));
  }

  public static T ConvertTo<T>(object value, T defaultValue)
  {
    if (value == DBNull.Value)
    {
      return defaultValue;
    }
    return (T) ChangeType(value, typeof(T));
  }

  public static object ChangeType(object value, Type conversionType)
  {
    if (conversionType == null)
    {
      throw new ArgumentNullException("conversionType");
    }

    // if it's not a nullable type, just pass through the parameters to Convert.ChangeType
    if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
    {
      // null input returns null output regardless of base type
      if (value == null)
      {
        return null;
      }

      // it's a nullable type, and not null, which means it can be converted to its underlying type,
      // so overwrite the passed-in conversion type with this underlying type
      conversionType = Nullable.GetUnderlyingType(conversionType);
    }
    else if (conversionType.IsEnum)
    {
      // strings require Parse method
      if (value is string)
      {
        return Enum.Parse(conversionType, (string) value);          
      }
      // primitive types can be instantiated using ToObject
      else if (value is int || value is uint || value is short || value is ushort || 
           value is byte || value is sbyte || value is long || value is ulong)
      {
        return Enum.ToObject(conversionType, value);
      }
      else
      {
        throw new ArgumentException(String.Format("Value cannot be converted to {0} - current type is " +
                              "not supported for enum conversions.", conversionType.FullName));
      }
    }

    return Convert.ChangeType(value, conversionType);
  }
}

Тогда ваша реализация GetQueryString <T> может быть:

public static T GetQueryString<T>(string key)
{
    T result = default(T);
    string value = HttpContext.Current.Request.QueryString[key];

    if (!String.IsNullOrEmpty(value))
    {
        try
        {
            result = Converter.ConvertTo<T>(value);  
        }
        catch
        {
            //Could not convert.  Pass back default value...
            result = default(T);
        }
    }

    return result;
}

0

Мне нравится начинать с такого класса, как этот class settings {public int X {get; set;} public string Y {get; набор; } // повторяем при необходимости

 public settings()
 {
    this.X = defaultForX;
    this.Y = defaultForY;
    // repeat ...
 }
 public void Parse(Uri uri)
 {
    // parse values from query string.
    // if you need to distinguish from default vs. specified, add an appropriate property

 }

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.