Domanda

Diciamo che ho un oggetto dati, ma questo oggetto può contenere uno dei diversi tipi di dati.

class Foo
{
    int intFoo;
    double doubleFoo;
    string stringFoo;
}

Ora, voglio creare un accessor. Un modo per ottenere questi dati. Ovviamente, potrei creare più accessor:

public int GetIntFoo();
public double GetDoubleFoo();
public string GetStringFoo();

O potrei creare più proprietà

public int IntFoo { get; set; }
public double DoubleFoo { get; set; }
public string StringFoo { get; set; }

Non credo che questo sia un ottimo design. Richiede che il codice client sia più interessato al tipo di quanto dovrebbe essere. Inoltre, ho davvero bisogno di un solo valore per questa classe e quanto sopra consentirebbe di assegnare uno di ciascun tipo contemporaneamente. Non va bene.

Un'opzione è usare Generics.

class Foo<T>
{
    public T TheFoo { get; set; }
}

Tuttavia, questo non crea un Foo, ma crea un Foo < T > ;. Un tipo diverso per ciascuno, quindi non posso davvero usarli come lo stesso tipo.

Potrei derivare Foo < T > da FooBase, quindi trattali tutti come FooBase, ma poi sono tornato al problema di accedere ai dati.

Un'opzione Generics diversa consiste nell'utilizzare qualcosa del genere:

class Foo
{
    string stringRepresentationOfFoo;
    public T GetFoo<T>() { return /* code to convert string to type */ }
}

Ovviamente il problema è che qualsiasi tipo di T potrebbe essere passato e, francamente, è un po 'occupato.

Potrei anche solo boxare i valori e restituire un oggetto, ma poi non c'è sicurezza del tipo.

Idealmente, voglio trattare tutti gli Foo allo stesso modo, ma voglio la sicurezza del tipo in modo che se non c'è uno StringFoo, non posso nemmeno compilare un riferimento a uno StringFoo.

Foo foo = new Foo("Foo");
string sFoo = foo.Value;  // succeeds.
&nbsp;
Foo foo = new Foo(0);
int iFoo = foo.Value; // succeeds
string sFoo = foo.Value; // compile error

Forse questo non è nemmeno possibile .. e dovrò scendere a compromessi, ma forse mi sto perdendo qualcosa.

Qualche idea?

EDIT:

Ok, quindi, come sottolinea Daniel, il controllo del tempo di compilazione di un tipo di runtime non è pratico.

Qual è la mia migliore opzione per fare ciò che voglio fare qui? Vale a dire, trattare tutti Foo è lo stesso, ma avere ancora un meccanismo di accesso relativamente sano?

EDIT2:

Non voglio convertire il valore in diversi tipi. Voglio restituire il tipo corretto per il valore. Cioè, se è un doppio, non voglio restituire un int.

È stato utile?

Soluzione

Che ne dici di passare la variabile come parametro per ottenere? In questo modo:

int i = foo.get(i);

Quindi nella tua classe, avresti qualcosa del tipo:

public int get(int p) {
    if(this.type != INTEGER) throw new RuntimeException("Data type mismatch");
    return this.intVal;
}

public float get(float p) {
    if(this.type != FLOAT) throw new RuntimeException("Data type mismatch");
    return this.floatVal;
}

Questo tipo di rovescia il controllo del tipo: invece di controllare il tipo di foo, devi controllare il tipo che desideri. Se può darti quel tipo, lo fa, altrimenti genera un'eccezione di runtime.

Altri suggerimenti

Non penso che potrebbe funzionare (dandoti l'errore del compilatore che desideri)

Cosa vorresti che facesse:

Foo bar = (new Random()).Next(2) == 0 ? new Foo("bar") : new Foo(1);
int baz = bar.Value;

È un errore del compilatore?

Penso che " trattali tutti allo stesso " (almeno nel modo in cui l'hai descritto) e " errore di compilazione " si escluderanno a vicenda.

In ogni caso, penso che il " il modo migliore " sarà un compromesso tra generici ed ereditarietà. È possibile definire un Foo<T> che è una sottoclasse di Foo; allora puoi ancora avere raccolte di Foo.

abstract public class Foo
{
  // Common implementation

  abstract public object ObjectValue { get; }
}

public class Foo<T> : Foo
{
   public Foo(T initialValue)
   {
      Value = initialValue;
   }

   public T Value { get; set; }

   public object ObjectValue
   { 
      get { return Value; }
   }
}

Molti sistemi usano un metodo helper per restituire i tipi alternativi proprio come l'oggetto base framework .net ha il metodo ToString ()

Scegli qual è il tipo di base migliore per ciascuno dei tuoi oggetti e fornisci i metodi per gli altri casi

per es.

class Foo{
    public Int32 Value { get; set; }
    public Byte ToByte() { return Convert.ToByte(Value); }
    public Double ToDouble() { return (Double)Value; }
    public new String ToString() { return Value.ToString("#,###"); }
}

Una cosa è archiviare qualsiasi tipo nel tuo stato interno della classe, e un'altra è esporla esternamente. Quando scrivi una classe, dichiari effettivamente un contratto per il suo comportamento. Il modo in cui lo scrivi influenzerà notevolmente l'aspetto del codice client quando usi la classe.

Ad esempio, implementando l'interfaccia IConvertible indica che il tuo tipo può essere convertito in qualsiasi tipo CLR come valore equivalente.

Ho anche visto implementazioni in cui è stata utilizzata una classe Value per memorizzare i risultati dei calcoli, risultati che potrebbero rappresentare una stringa, double, int o boolean. Ma il problema era che il codice client doveva controllare una proprietà Value.Type di un enum {String, Integer, Double, Boolean} e quindi lanciare la proprietà Value.Value (che veniva esposta esternamente dalla classe Value come tipo di oggetto ) o utilizzare i getter specifici ValueString, ValueDouble, ValueInt, ValueBoolean.

Perché non usare solo string, double e int?


Dopo le informazioni sulla raccolta: che dire dell'utilizzo di object? Dovrai comunque verificare la presenza di tipi e simili. E per aiutarti, puoi usare gli operatori is e as. E il Enumerable.Cast Method , o ancora meglio, il Metodo Enumerable.OfType .

In realtà, qual è lo scopo di questa classe? Il problema più grande sembra essere la rottura del design almeno SRP (principio di responsabilità singola).

Tuttavia, se lo sto leggendo correttamente, desideri archiviare un valore nel contenitore, passare il contenitore al client e recuperare il valore in modo sicuro.

Con questo approccio, puoi utilizzare la tua proposta, ad esempio

namespace Project1 {
public class Class1 {
    static int Main(string[] args) {
        Foo a = new Foo();
        a.SetValue(4);
        Console.WriteLine(a.GetValue<int>());

        Foo b = new Foo();
        a.SetValue("String");
        Console.WriteLine(a.GetValue<string>());

        Console.ReadLine();

        return 0;
    }
}


class Foo {
    private object value; // watch out for boxing here!
    public void SetValue(object value) {
        this.value = value;
    }

    public T GetValue<T>() {
        object val = this.value;
        if (val == null) { return default(T); } // or throw if you prefer
        try {
            return (T)val;
        }
        catch (Exception) {
            return default(T);
            // cast failed, return default(T) or throw
        }
    }
}
}

Tuttavia, in quel caso perché non semplicemente passare i dati come oggetto e trasmetterli da soli?

A seconda delle tue esigenze, puoi anche provare " PHP in C # " ;:

namespace Project1 {
public class Class1 {
    static int Main(string[] args) {
        MyInt a = 1;
        MyInt b = "2";

        Console.WriteLine(a + b); // writes 3

        Console.ReadLine();

        return 0;
    }
}


class MyInt {
    private int value;

    public static implicit operator int(MyInt container) {
        return container.value;
    }

    public static implicit operator MyInt(int value) {
        MyInt myInt = new MyInt();
        myInt.value = value;
        return myInt ;
    }

    public static implicit operator MyInt(string stringedInt) {
        MyInt myInt = new MyInt();
        myInt.value = int.Parse(stringedInt);
        return myInt;
    }
}
}

Mi dispiace, non compro la tua premessa. Se tutti i dati hanno lo stesso scopo, dovrebbero avere tutti lo stesso tipo. Considera una classe che ha lo scopo di mantenere la temperatura corrente, come restituito da uno dei numerosi servizi web. Tutti i servizi restituiscono la temperatura in gradi centigradi. Ma uno ritorna come int, uno ritorna come doppio e uno lo restituisce come stringa.

Non sono tre tipi diversi - è un tipo - doppio. Dovresti semplicemente convertire i rendimenti non doppi in doppio, che è la temperatura (o forse il galleggiante).

In generale, se hai più rappresentazioni di una cosa, allora è ancora una cosa, non più cose. Converti le rappresentazioni multiple in una.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top