Tworzenie metody ogólnej w C #

84

Próbuję połączyć kilka podobnych metod w metodę ogólną. Mam kilka metod, które zwracają wartość kwerendy lub null, jeśli ten obiekt kwerendy nie istnieje lub ma nieprawidłowy format. Byłoby to wystarczająco łatwe, gdyby wszystkie typy natywnie dopuszczały wartość null, ale muszę użyć typu ogólnego dopuszczającego wartość null dla liczb całkowitych i dat.

Oto, co mam teraz. Jednak przekaże z powrotem 0, jeśli wartość liczbowa jest nieprawidłowa i niestety jest to poprawna wartość w moich scenariuszach. Czy ktoś może mi pomóc? Dzięki!

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;
}
Mike Cole
źródło
Portuje kilka implementacji, więc wywołaj starą funkcjonalność, zapamiętaj wynik, wywołaj nową funkcjonalność, zapamiętaj wynik, porównaj. Teraz zrób to 100 razy z kilkoma przypadkowymi danymi wejściowymi i voila!
Hamish Grubijan
Przepraszam, nadal nie rozumiem, jak to się dzieje w tym przypadku. Nadal staram się, aby ta funkcja działała.
Mike Cole
Patrząc na odpowiedzi, jestem trochę zdezorientowany: czy twoi rozmówcy parametryzują za pomocą int czy int? jak T?
Wydaje mi się, że zamiast zajmować się tym wewnętrznie, powinieneś pozwolić metodzie zgłosić wyjątek. Może to tylko ja, ale ktoś może być zdezorientowany, dlaczego jego wywołanie zawsze zwraca wartość domyślną, ponieważ nie widzi wyjątku, który jest generowany w przypadku ChangeTypeniepowodzenia.
zmiażdż

Odpowiedzi:

64

Co się stanie, jeśli określisz wartość domyślną do zwrócenia, zamiast używać wartości domyślnej (T)?

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

Ułatwia też dzwonienie:

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

Wadą jest to, że potrzebujesz magicznych wartości, aby oznaczyć nieprawidłowe / brakujące wartości zapytania.

Będzie
źródło
Tak, wydaje się to bardziej opłacalne niż poleganie na domyślnej wartości liczby całkowitej. Będę o tym pamiętać. Nadal mam nadzieję, że moja oryginalna funkcja będzie działać dla wszystkich typów, chociaż mogę po prostu uciec się do korzystania z funkcji innych niż ogólne.
Mike Cole
Dlaczego po prostu nie zwrócić wartości innej niż zero dla nieprawidłowej liczby całkowitej? Możesz zwrócić wszystko, co chcesz, co nie jest poprawną wartością lub ma już specjalny cel, na przykład null. Możesz nawet stworzyć swój własny typ o nazwie InvalidInteger lub coś w tym stylu. Zwracasz wartość null za zły querystring, prawda? Możesz zwrócić to również dla nieprawidłowej liczby całkowitej, więc wartość null oznaczałaby po prostu „coś jest nie tak i nie mam dla ciebie wartości”, a może przekazać kod powodu przez odniesienie do funkcji?
Dan Csharpster
1
Jak uzyskać wartość dla: long ? testgdzie wartość domyślna powinna być pusta
Arshad
16

Wiem, wiem, ale ...

public static bool TryGetQueryString<T>(string key, out T queryString)
Sójka
źródło
4
Try-Pattern powinny być dobrze znane każdej programista .NET. Nie jest źle, jeśli o mnie chodzi. W F # lub NET 4.0 użyłbyś opcji (lub wyboru)
Christian Klauser
6
Jeśli z żadnego innego powodu, staram się tego unikać, ponieważ nie znoszę konieczności „wstępnego deklarowania” tej zmiennej wyjściowej, zwłaszcza jeśli nigdy nie zostanie ona wykorzystana - marnotrawstwo tego, co w przeciwnym razie mogłoby być doskonale dobrym wierszem kodu.
Jay
Właściwie jest to najprostszy sposób rozwiązania twojego problemu - zdefiniuj jedną funkcję jak powyżej + dwa pomocniki, które będą jej używać (byłyby to 4 linery).
greenoldman
1
Nienawidzę wzoru Try z tego samego powodu, co stwierdził Jay. Wolałbym jedną ogólną funkcję, jeśli to możliwe, co było moim pierwotnym celem.
Mike Cole
11
Zrobić czy nie zrobić, nie ma wyboru! <Yoda>
Rabbit
12

A co z tym? Zmień typ zwrotu z TnaNullable<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;
        }
Graviton
źródło
Błąd: typ „T” musi być typem wartości niepodlegającym wartości null, aby można go było użyć jako parametru „T” w typie ogólnym lub metodzie „System.Nullable <T>”.
Mike Cole
Musisz również określić where T : struct.
Aaronaught
@Mike C: Nie powinieneś otrzymywać tego samego błędu. Edytowany kod zdecydowanie się kompiluje.
Aaronaught
Tak, mam to teraz. Więc co się dzieje, gdy chcę to wywołać dla typu String? Nie zaakceptuje tego, jak jest teraz.
Mike Cole
@MikeC, nie myśl, że to możliwe, ponieważ stringjest to nullablewartość
Graviton
5

Możesz użyć czegoś w rodzaju może monady (chociaż wolałbym odpowiedź Jaya)

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

Twoja metoda wyglądałaby następująco:

    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>();
    }
Artem Govorov
źródło
4

Convert.ChangeType()nie obsługuje poprawnie typów dopuszczających wartość null lub wyliczeń w .NET 2.0 BCL (myślę, że jest to poprawione dla BCL 4.0). Zamiast komplikować zewnętrzną implementację, spraw, aby konwerter wykonywał więcej pracy za Ciebie. Oto implementacja, której używam:

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

Wtedy Twoja implementacja GetQueryString <T> może wyglądać następująco:

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;
}
Sam
źródło
0

Lubię zaczynać od klasy takiej jak ta class settings {public int X {get; set;} public string Y {get; zestaw; } // powtórz w razie potrzeby

 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

 }

To działało dobrze w setkach projektów. Do analizowania wartości można użyć jednego z wielu innych rozwiązań do analizowania.

Brak zwrotów Brak zwrotów
źródło