Proprietà astratta nella classe base per forzare il programmatore a definirlo


10

Sto codificando con un modello di stato per un dispositivo incorporato. Ho una classe base / astratta chiamata State e quindi ogni classe di stato discreta (concreta) implementa la classe State astratta.

Nella classe di stato ho diversi metodi astratti. Se non implemento i metodi astratti nella classe discreta (concreta), Visual Studio genererà un errore simile al seguente:

... L'errore 1 "myConcreteState" non implementa il membro astratto ereditato "myAbstractState"

Ora: sto cercando di creare una proprietà String per ogni stato chiamato StateName. Ogni volta che creo una nuova classe concreta, devo definire StateName. Voglio che VS lanci un errore se non lo uso. C'è un modo semplice per fare questo?

Ho provato questo nella classe astratta / base:

public abstract string StateName { get; set; }

Ma non ho bisogno di implementare i metodi Get e Set in ogni stato.

Domanda rivista: in una situazione ideale, ogni classe di stato dovrebbe avere StateName definito ed essere ereditato dalla classe di base astratta.

StateName = "MyState1"; //or whatever the state's name is

Se manca tale istruzione, Visual Studio genererà un errore come descritto sopra. È possibile e, se sì, come?


2
Non capisco la domanda.
Ben Aaronson,

Immagino che il modo "corretto" per farlo sia quello di avere un costruttore protetto sulla classe base che richieda il nome dello stato come parametro.
Roman Reiner,

@RomanReiner Ho pensato di farlo anch'io..ma sembra ridondante perché ogni volta che cambia / chiama uno stato, dovrei digitare il nome.
GisMofx,

@BenAaronson Ho chiarito la mia domanda in fondo.
GisMofx,

Il nome dello stato è costante per tipo o costante per istanza?
Roman Reiner,

Risposte:


16

Immagino che il modo "corretto" per farlo sia quello di avere un costruttore protetto sulla classe base che richieda il nome dello stato come parametro.

public abstract class State
{
    private readonly string _name;

    protected State(string name)
    {
        if(String.IsNullOrEmpty(name))
            throw new ArgumentException("Must not be empty", "name");

        _name = name;
    }

    public string Name { get { return _name; } }
}

Gli stati concreti forniscono quindi un costruttore pubblico che chiama implicitamente il costruttore della classe base con il nome appropriato.

public abstract class SomeState : State
{
    public SomeState() : base("The name of this state")
    {
        // ...
    }
}

Poiché la classe base non espone altri costruttori (né protetti né pubblici), ogni classe ereditaria deve passare attraverso questo singolo costruttore e quindi deve definire un nome.

Si noti che non è necessario fornire il nome quando si crea un'istanza di uno stato concreto perché il suo costruttore si occupa di questo:

var someState = new SomeState(); // No need to define the name here
var name = someState.Name; // returns "The name of this state"

1
Grazie! In realtà, il mio costruttore della classe base ora accetta un argomento aggiuntivo; quindi ho due ingressi. Non appena ho impostato questo, ottengo errori che mi servono un argomento aggiuntivo nei costruttori di classe di stato ...:base(arg1, arg2)! Questa è una soluzione che stavo cercando. Questo aiuta davvero a mantenere il mio codice di stato più coerente.
GisMofx,

3

A partire da C # 6 (credo - C #: The New and Improved C # 6.0 ) sei in grado di creare solo proprietà getter. Quindi potresti dichiarare la tua classe base in questo modo -

public abstract class State
{
    public abstract string name { get; }

    // Your other functions....
}

E poi nella tua sottoclasse puoi implementare in questo Statemodo -

public class SomeState : State
{
    public override string name { get { return "State_1"; } }
}

O anche più ordinato usando l'operatore lambda -

public class SomeState : State
{
    public override string name => "State_1";
}

Entrambi restituirebbero sempre "State_1" e sarebbero immutabili.


1
questo può essere scritto public override string name { get; } = "State_1";che quindi non ha bisogno di rivalutare il valore ogni volta.
Dave Cousineau,

2

I tuoi requisiti sono una contraddizione:

Sto cercando di creare una proprietà String per ogni stato chiamato StateName.

vs.

Ma non ho bisogno di implementare tutto ciò in ogni Stato.

Non esiste una funzione linguistica che ti consenta di forzare l'esistenza di un membro solo in alcune sottoclassi. Dopotutto, il punto di usare una superclasse è fare affidamento sul fatto che tutte le sottoclassi avranno tutti i membri della superclasse (e forse anche di più). Se vuoi creare classi che fungono da Statema non hanno un nome, per definizione, non dovrebbero (/ possono) essere sottoclassi di State.

Devi cambiare le tue esigenze o usare qualcosa di diverso dalla semplice eredità.

Una possibile soluzione con il codice così com'è potrebbe essere quella di rendere il metodo Statenon astratto e restituire una stringa vuota.


Penso che tu abbia preso quella seconda affermazione fuori contesto, ma chiarirò. Non ho bisogno dei metodi Get / Set, voglio semplicemente StateName = "MyState1"in ogni stato. Se non ho questa affermazione nella classe di stato, idealmente Visual Studio genererà un errore.
GisMofx,

3
@GisMofx Se si desidera forzare il nome dello stato in ciascuna sottoclasse, rendere astratto ottenere ma non richiedere uno stato. Quindi ogni sottoclasse dovrà fornire return "MyState1"la propria implementazione e potrà aggiungere spazio di archiviazione mutabile per il valore se necessario. Tuttavia, è necessario chiarire i requisiti della domanda: ogni tipo di stato richiede un StateName che può essere letto e qualsiasi tipo di stato richiede che sia mutato dopo la creazione?
Pete Kirkham,

@PeteKirkham ogni stato richiede un nome, non deve essere modificato dopo la creazione. I nomi degli stati sono statici / immutabili.
GisMofx,
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.