Creazione di un metodo generico in C #


86

Sto cercando di combinare una serie di metodi simili in un metodo generico. Ho diversi metodi che restituiscono il valore di una querystring, o null se quella querystring non esiste o non è nel formato corretto. Questo sarebbe abbastanza facile se tutti i tipi fossero nativamente nullable, ma devo usare il tipo generico nullable per numeri interi e date.

Ecco cosa ho adesso. Tuttavia, restituirà uno 0 se un valore numerico non è valido e sfortunatamente è un valore valido nei miei scenari. Qualcuno può aiutarmi? Grazie!

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

Sta portando una serie di implementazioni, quindi chiama la vecchia funzionalità, ricorda il risultato, chiama la nuova funzionalità, ricorda il risultato, confronta. Ora fallo 100 volte con un mucchio di input casuali e voilà!
Hamish Grubijan

Mi dispiace, ancora non capisco come ciò si applichi in questo caso. Sto ancora cercando di far funzionare la funzione.
Mike Cole

Guardando le risposte, sono un po 'confuso: i tuoi chiamanti stanno parametrizzando usando int o int? come T?

Mi sembra che, invece di gestirlo internamente, dovresti consentire al metodo di generare l'eccezione. Forse sono solo io, ma qualcuno potrebbe essere confuso perché la loro chiamata restituisce sempre il valore predefinito poiché non vedono l'eccezione che viene generata quando ChangeTypefallisce.
schiacciare il

Risposte:


65

Cosa succede se si specifica il valore predefinito da restituire, invece di utilizzare predefinito (T)?

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

Rende anche più facile chiamarlo:

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

Lo svantaggio è che hai bisogno di valori magici per denotare valori di stringa di query non validi / mancanti.


Sì, questo sembra più fattibile che fare affidamento sul valore predefinito di un numero intero. Lo terrò a mente. Spero ancora di far funzionare la mia funzione originale per tutti i tipi, anche se potrei ricorrere all'uso di funzioni non generiche.
Mike Cole

Perché non restituire semplicemente qualcosa di diverso da zero per un numero intero non valido? Puoi restituire tutto ciò che desideri che non sia un valore valido o che abbia già uno scopo speciale, come null. Puoi persino creare il tuo tipo chiamato InvalidInteger o qualcosa del genere. Stai restituendo null per una stringa di query errata, giusto? Potresti restituirlo anche per un numero intero non valido, quindi null significherebbe semplicemente `` qualcosa non va e non ho valore per te '' e forse passare un reasonCode facendo riferimento alla funzione?
Dan Csharpster

1
Come ottenere il valore per: long ? testdove il valore predefinito dovrebbe essere nullo
Arshad

16

Lo so, lo so, ma ...

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

4
Il Try-pattern dovrebbe essere ben noto a qualsiasi sviluppatore .NET. Non è una brutta cosa se me lo chiedi. In F # o NET 4.0 useresti Option (o Choice)
Christian Klauser

6
Se per nessun altro motivo, cerco di evitarlo perché odio dover "pre-dichiarare" quella variabile di output, soprattutto se non viene nemmeno mai utilizzata - uno spreco di quella che altrimenti sarebbe stata una riga di codice perfettamente valida.
Jay,

In realtà è il modo più semplice per risolvere il tuo problema: definisci una funzione come sopra + due helper che utilizzerebbero questa funzione (quelli sarebbero 4 linee).
greenoldman

1
Odio lo schema Try per lo stesso motivo per cui ha affermato Jay. Preferirei una funzione generica, se possibile, che era il mio obiettivo originale.
Mike Cole

11
Fallo o no, non c'è tentativo! <Yoda>
Coniglio

12

Che dire di questo? Modificare il tipo di ritorno da TaNullable<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;
        }

Errore: il tipo "T" deve essere un tipo di valore non nullable per poterlo utilizzare come parametro "T" nel tipo o metodo generico "System.Nullable <T>".
Mike Cole,

Devi anche specificare where T : struct.
Aaronaught

@ Mike C: Non dovresti ricevere lo stesso errore. Il codice modificato viene definitivamente compilato.
Aaronaught

Sì, capito ora. Quindi cosa succede quando voglio chiamarlo per il tipo String? Non lo accetterà come è adesso.
Mike Cole,

@ MikeC, non credo che sia possibile perché stringè un nullablevalore
Graviton

5

Puoi usare una sorta di forse monade (anche se preferirei la risposta di Jay)

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

Il tuo metodo sarebbe simile a:

    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()non gestisce correttamente tipi o enumerazioni nullable in .NET 2.0 BCL (penso che sia corretto per BCL 4.0). Piuttosto che rendere l'implementazione esterna più complessa, fai in modo che il convertitore lavori di più per te. Ecco un'implementazione che utilizzo:

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

Quindi la tua implementazione di GetQueryString <T> può essere:

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

Mi piace iniziare con una classe come questa class settings {public int X {get; set;} public string Y {get; impostato; } // ripeti se necessario

 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

 }

Questo ha funzionato bene su centinaia di progetti. È possibile utilizzare una delle tante altre soluzioni di analisi per analizzare i valori.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.