Erstellen einer generischen Methode in C #

83

Ich versuche, eine Reihe ähnlicher Methoden zu einer generischen Methode zu kombinieren. Ich habe mehrere Methoden, die den Wert eines Querystrings zurückgeben, oder null, wenn dieser Querystring nicht existiert oder nicht im richtigen Format vorliegt. Dies wäre einfach genug, wenn alle Typen von Haus aus nullwertfähig wären, aber ich muss den generischen Typ nullbar für Ganzzahlen und Datumsangaben verwenden.

Folgendes habe ich jetzt. Es wird jedoch eine 0 zurückgegeben, wenn ein numerischer Wert ungültig ist, und dies ist in meinen Szenarien leider ein gültiger Wert. Kann mir jemand helfen? Vielen Dank!

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
quelle
Er portiert eine Reihe von Implementierungen. Rufen Sie also die alte Funktionalität auf, merken Sie sich das Ergebnis, rufen Sie die neue Funktionalität auf, merken Sie sich das Ergebnis, vergleichen Sie. Jetzt mach das 100 Mal mit einer Reihe von zufälligen Eingaben und voila!
Hamish Grubijan
Es tut mir leid, ich verstehe immer noch nicht, wie das in diesem Fall gilt. Ich versuche immer noch, die Funktion zum Laufen zu bringen.
Mike Cole
Wenn ich mir die Antworten ansehe, bin ich etwas verwirrt: Parametrieren Ihre Anrufer mit int oder int? als T?
Es scheint mir, dass Sie, anstatt dies intern zu behandeln, der Methode erlauben sollten, die Ausnahme auszulösen. Vielleicht bin ich es nur, aber jemand ist möglicherweise verwirrt, warum sein Anruf immer den Standardwert zurückgibt, da er die Ausnahme nicht sieht, die generiert wird, wenn ein ChangeTypeFehler auftritt.
Crush

Antworten:

64

Was ist, wenn Sie den Standardwert angegeben haben, der zurückgegeben werden soll, anstatt Standard (T) zu verwenden?

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

Es macht es auch einfacher, es anzurufen:

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

Der Nachteil ist, dass Sie magische Werte benötigen, um ungültige / fehlende Querystring-Werte zu kennzeichnen.

Wille
quelle
Ja, dies scheint praktikabler zu sein, als sich auf den Standardwert einer Ganzzahl zu verlassen. Ich werde das im Hinterkopf behalten. Ich hoffe immer noch, dass meine ursprüngliche Funktion für alle Typen funktioniert, obwohl ich möglicherweise nicht generische Funktionen verwende.
Mike Cole
Warum nicht einfach etwas anderes als Null für eine ungültige Ganzzahl zurückgeben? Sie können alles zurückgeben, was Sie möchten, das entweder kein gültiger Wert ist oder bereits einen speziellen Zweck hat, wie z. B. null. Sie können sogar einen eigenen Typ namens InvalidInteger oder so erstellen. Sie geben null für einen schlechten Querystring zurück, oder? Sie könnten das auch für eine ungültige Ganzzahl zurückgeben, also würde null einfach "etwas ist schlecht und ich habe keinen Wert für Sie" bedeuten und vielleicht einen reasonCode als Verweis auf die Funktion übergeben?
Dan Csharpster
1
So erhalten Sie einen Wert für: long ? testwobei der Standardwert null sein sollte
Arshad
16

Ich weiß, ich weiß, aber ...

public static bool TryGetQueryString<T>(string key, out T queryString)
Jay
quelle
4
Das TryMuster sollte jedem .NET-Entwickler bekannt sein. Es ist keine schlechte Sache, wenn Sie mich fragen. In F # oder NET 4.0 würden Sie Option (oder Auswahl)
Christian Klauser
6
Wenn auch aus keinem anderen Grund, versuche ich es zu vermeiden, weil ich es hasse, diese Ausgabevariable "vordeklarieren" zu müssen, insbesondere wenn sie nicht einmal verwendet wird - eine Verschwendung dessen, was sonst eine vollkommen gute Codezeile gewesen wäre.
Jay
Eigentlich ist es der einfachste Weg, um Ihr Problem zu lösen - definieren Sie eine Funktion wie oben + zwei Helfer, die diese Funktion verwenden würden (das wären 4 Liner).
Greenoldman
1
Ich hasse das Try-Muster aus dem gleichen Grund, wie Jay es angegeben hat. Ich würde wenn möglich eine generische Funktion bevorzugen, was mein ursprüngliches Ziel war.
Mike Cole
11
Tun oder nicht, es gibt keinen Versuch! <Yoda>
Kaninchen
12

Was ist damit? Ändern Sie den Rückgabetyp von TnachNullable<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
quelle
Fehler: Der Typ 'T' muss ein nicht nullbarer Werttyp sein, um ihn als Parameter 'T' im generischen Typ oder in der generischen Methode 'System.Nullable <T>' zu verwenden.
Mike Cole
Sie müssen auch angeben where T : struct.
Aaronaught
@ Mike C: Sie sollten nicht den gleichen Fehler erhalten. Der bearbeitete Code wird definitiv kompiliert.
Aaronaught
Ja, hab es jetzt verstanden. Was passiert also, wenn ich dies für den String-Typ aufrufen möchte? Es wird es nicht so akzeptieren, wie es jetzt ist.
Mike Cole
@ MikeC, denke nicht, dass das möglich ist, weil stringes ein istnullable Wert ist
Graviton
5

Sie können eine Art Vielleicht-Monade verwenden (obwohl ich Jays Antwort vorziehen würde)

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

Ihre Methode würde folgendermaßen aussehen:

    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
quelle
4

Convert.ChangeType()behandelt nullfähige Typen oder Aufzählungen in .NET 2.0 BCL nicht korrekt (ich denke, es ist jedoch für BCL 4.0 behoben). Lassen Sie den Konverter mehr Arbeit für Sie erledigen, anstatt die äußere Implementierung komplexer zu gestalten. Hier ist eine Implementierung, die ich verwende:

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

Dann kann Ihre Implementierung von GetQueryString <T> wie folgt sein:

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
quelle
0

Ich beginne gerne mit einer Klasse wie dieser. Klasseneinstellungen {public int X {get; set;} public string Y {get; einstellen; } // nach Bedarf wiederholen

 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

 }

Dies hat bei Hunderten von Projekten gut funktioniert. Sie können eine der vielen anderen Parsing-Lösungen verwenden, um Werte zu analysieren.

Keine Rückerstattung Keine Rückgabe
quelle