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


84

Я намагаюся поєднати купу подібних методів у загальний метод. У мене є декілька методів, які повертають значення рядка запиту, або значення 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

Вибачте, я досі не розумію, як це стосується цього випадку. Я все ще намагаюся змусити функцію працювати.
Mike Cole

Дивлячись на відповіді, я трохи заплутався: ваші параметри параметризують за допомогою int або int? як Т?

Мені здається, що замість того, щоб обробляти це внутрішньо, ви повинні дозволити методу викинути виняток. Можливо, це лише я, але хтось може заплутатися, чому їх виклик завжди повертає значення за замовчуванням, оскільки вони не бачать винятку, який генерується, коли ChangeTypeне вдається.
розчавити

Відповіді:


64

Що робити, якщо ви вказали значення за замовчуванням, яке потрібно повернути, замість використання значення за замовчуванням (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

Недоліком є ​​те, що вам потрібні магічні значення для позначення недійсних / відсутніх значень рядка запитів.


Так, це здається більш життєздатним, ніж покладатися на значення за замовчуванням цілого числа. Я буду це пам’ятати. Я все ще сподіваюся, що моя оригінальна функція працює для всіх типів, хоча я просто можу вдатися до використання не загальних функцій.
Mike Cole

Чому б просто не повернути щось інше, ніж нуль, для недійсного цілого числа? Ви можете повернути все, що завгодно, яке не є дійсним значенням або вже має спеціальне призначення, наприклад null. Ви навіть можете створити, створити власний тип під назвою InvalidInteger або щось інше. Ви повертаєте null за неправильний рядок запиту, так? Ви можете повернути це також для недійсного цілого числа, тому null означало б просто "щось погане, і я не маю для вас значення", і, можливо, передати reasonCode, посилаючись на функцію?
Dan Csharpster

1
Як отримати значення для: long ? testде за замовчуванням має бути нуль
Аршад

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
Я ненавиджу шаблон Try з тієї ж причини, що і заявляв Джей. Я б віддав перевагу одній загальній функції, якщо це можливо, що було моєю початковою метою.
Mike Cole

11
Робіть чи ні, спроби немає! <Yoda>
Кролик

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' повинен бути типом значення, що не допускає нульового значення, щоб використовувати його як параметр 'T' у загальному типі або методі 'System.Nullable <T>'.
Mike Cole

Також потрібно вказати where T : struct.
Aaronaught

@Mike C: Ви не повинні отримувати однакову помилку. Відредагований код, безумовно, компілюється.
Aaronaught

Так, зрозумів зараз. Отже, що трапляється, коли я хочу викликати це для типу String? Він не прийме цього, як зараз.
Mike Cole

@MikeC, не думай, що це можливо, тому що stringце nullableцінність
Graviton

5

Ви можете скористатися якоюсь монадою (хоча я б віддав перевагу відповіді Джея)

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()неправильно обробляє типи чи перерахування, що допускають обнулення, у .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

Мені подобається починати з класу, подібного до цього налаштування класу {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

 }

Це добре працювало над 100 проектами. Ви можете використовувати одне з багатьох інших рішень для синтаксичного аналізу значень.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.