Abilitare JavaScript per vedere questo sito.

Generalità

Costruttori classe

Inizializzazione membri classe

Distruttori classe

Metodi classe

Ereditarietà e polimorfismo

Membri protetti e privati classe

Sovraccarico operatori

Accessi alle proprietà

Le classi di script sono dichiarate globalmente e forniscono un modo semplice per raggruppare proprietà e metodi in unità logiche. La sintassi delle classi è simile a quella di C++ e Java.

Generalità

Con le classi lo script writer può dichiarare nuovi tipi di dati che contengono gruppi di proprietà e metodi per manipolarli.

Le classi di script sono tipi di riferimento, il che significa che è possibile mantenere più riferimenti o handle per la stessa istanza di oggetto. Le classi utilizzano una gestione automatica della memoria, per cui le istanze degli oggetti vengono distrutte solo quando l'ultimo riferimento all'istanza viene cancellato.

// The class declaration
class MyClass
{
  // A class method
  void DoSomething() {}
  // A class properties
  int a;
  int b;
}

Una classe può implementare metodi specifici per sovraccaricare gli operatori. Questo può semplificare il modo in cui le istanze dell'oggetto vengono utilizzate nelle espressioni, in modo che non sia necessario nominare esplicitamente ogni funzione, ad esempio il metodo opAdd si traduce nell'operatore +.

Un'altra caratteristica utile è la possibilità di implementare gli accessi alle proprietà, che possono essere utilizzati sia per fornire proprietà virtuali, cioè che sembrano proprietà ma in realtà non lo sono, sia per implementare routine specifiche che devono essere eseguite ogni volta che si accede a una proprietà.

Una classe di script può anche ereditare da altre classi e implementare interfacce.

Costruttori classe

I costruttori di classe sono metodi specifici che verranno utilizzati per creare nuove istanze della classe. Non è obbligatorio per una classe dichiarare i costruttori, ma farlo può rendere più facile l'uso della classe, poiché non sarà necessario istanziare la classe e poi impostare manualmente le proprietà.

I costruttori sono dichiarati senza un tipo di ritorno e devono avere lo stesso nome della classe stessa. È possibile implementare più costruttori con diversi elenchi di parametri per diverse forme di inizializzazione.

class MyClass
{
  // Implement a default constructor
  MyClass()
  {
  }
  // Implement the copy constructor
  MyClass(const MyClass &inout other)
  {
    // Copy the value of the other instance
  }
  // Implement other constructors with different parameter lists
  MyClass(int a, string b) {}
  MyClass(float x, float y, float z) {}
  // Implement conversion constructors
  // The second can only be used explicitly
  MyClass(int a) {}
  MyClass(string a) explicit {}
}

Il costruttore di copia è un costruttore specifico che il compilatore può utilizzare per creare codice più performante quando è necessario creare copie di un oggetto. Senza il costruttore di copia, il compilatore sarà costretto a istanziare prima la copia usando il costruttore predefinito e poi a copiare gli attributi con il metodo opAssign.

Un costruttore che accetta un singolo argomento può essere usato nelle conversioni di tipo. Per impostazione predefinita, il compilatore può utilizzarli per eseguire la conversione in modo implicito, se necessario. Se non lo si desidera, è possibile vietarlo aggiungendo il decoratore esplicito dopo il costruttore.

Un costruttore non può chiamare un altro costruttore. Se si desidera condividere le implementazioni nei costruttori, si deve utilizzare un metodo specifico per questo.

Il modo in cui i membri devono essere inizializzati può anche essere definito direttamente nella dichiarazione dei membri. In questo caso, l'espressione di inizializzazione verrà automaticamente compilata nel costruttore, senza che sia necessario scrivere nuovamente l'inizializzazione.

Costruttori generati automaticamente

In alcuni casi, il compilatore genera automaticamente un costruttore predefinito e un costruttore di copia.

Il costruttore predefinito viene generato automaticamente se non viene dichiarato esplicitamente un altro costruttore. Questo costruttore richiama semplicemente il costruttore predefinito per tutti i membri dell'oggetto e imposta tutti gli handle su null, a meno che i membri non abbiano inizializzazioni esplicite, nel qual caso queste vengono eseguite. Qualsiasi errore di compilazione nell'inizializzazione dei membri sarà segnalato come di consueto.

Il costruttore di copia viene generato automaticamente se non viene dichiarato esplicitamente un costruttore di copia. Questo costruttore cercherà di eseguire un costrutto di copia per ogni membro, oppure, se non è disponibile alcun costruttore di copia, eseguirà prima un costrutto predefinito seguito da un'assegnazione. Se viene riscontrato un errore di compilazione, ad esempio se un membro non può essere copiato, il costruttore di copia non verrà generato e il messaggio di errore verrà ignorato.

Se i costruttori generati automaticamente non sono desiderati, è possibile escluderli esplicitamente segnalandoli come cancellati.

class MyClass
{
  MyClass() delete;
  MyClass(const MyClass &inout) delete;
}

Inizializzazione membri classe

L'ordine in cui i membri della classe vengono inizializzati durante la costruzione di un oggetto diventa importante quando si utilizza l'ereditarietà o quando si definisce l'inizializzazione dei membri direttamente nella dichiarazione. Se si accede a un membro prima che sia stato inizializzato, lo script può causare un'eccezione di accesso a null handle, che interromperà l'esecuzione dello script.

Per una classe semplice, l'ordine in cui i membri vengono inizializzati è lo stesso in cui sono stati dichiarati. Se nella dichiarazione dei membri vengono fornite inizializzazioni esplicite, questi membri vengono inizializzati per ultimi.

// The order of this class will be: a, c, b, d
class Foo
{
  string a;
  string b = a;
  string c;
  string d = b;
}

Quando si usa l'ereditarietà, i membri della classe derivata senza inizializzazione esplicita saranno inizializzati prima di quelli della classe base, mentre i membri con inizializzazione esplicita saranno inizializzati dopo quelli della classe base.

// The order of this class will be: a, b
class Bar
{
  string a;
  string b = a;
}
// The order of this class will be: d, a, b, c
class Foo : Bar
{
  string c = a;
  string d;
}

Questo ordine di inizializzazione è stato scelto per evitare la maggior parte dei problemi legati all'accesso ai membri prima che siano stati inizializzati.

Tutti i membri sono inizializzati immediatamente all'inizio del costruttore definito, in modo che il resto del codice nel costruttore possa accedere ai membri senza problemi. Fa eccezione il caso in cui il costruttore inizializzi esplicitamente una classe base chiamando super(); in questo caso i membri con inizializzazione esplicita rimarranno non inizializzati fino a quando la classe base non sarà stata completamente costruita.

class Bar
{
  Bar(string val) { a = val; }
  string a;
}
class Foo : Bar
{
  Foo()
  {
    // b is already initialized here
    super(b); // a will be initialized in this call
      // c is initialized right after super() returns
  }
  string b;
  string c = a;
}

Si deve fare attenzione ai casi in cui un costruttore o l'inizializzazione di un membro chiamano i metodi della classe. Poiché i metodi della classe possono essere sovrascritti dalle classi derivate, è possibile che una classe base acceda involontariamente a un membro della classe derivata prima che sia stato inizializzato.

class Bar
{
  Bar()
  {
    DoSomething();
  }
  void DoSomething() {}
}

// This class will cause a null handle exception, because the Bar's constructor calls
// the DoSomething() method that accesses the member msg before it has been initialized.
class Foo : Bar
{
  string msg = 'hello';
  void DoSomething()
  {
    print(msg);
  }
}

Distruttori classe

Normalmente non è necessario implementare il distruttore di classe, poiché lo script avanzato libererà per default tutte le risorse che l'oggetto detiene quando viene distrutto. Tuttavia, ci possono essere situazioni in cui è necessario eseguire una routine di pulizia più esplicita come parte della distruzione dell'oggetto.

Il distruttore viene dichiarato in modo simile al costruttore, tranne per il fatto che deve essere preceduto dal simbolo ~ (noto anche come operatore bitwise not).

class MyClass
{
  // Implement the destructor if explicit cleanup is needed
  ~MyClass()
  {
    // Perform explicit cleanup here
  }
}

Si noti che lo script avanzato utilizza la gestione automatica della memoria con la garbage collection, quindi potrebbe non essere sempre facile prevedere quando il distruttore viene eseguito. Lo script avanzato chiamerà inoltre il distruttore una sola volta, anche se l'oggetto viene resuscitato aggiungendo un riferimento ad esso durante l'esecuzione del distruttore.

Non è possibile invocare direttamente il distruttore. Se si desidera invocare direttamente la pulizia, è necessario implementare un metodo pubblico per questo.

Metodi classe

I metodi di classe sono implementati allo stesso modo delle funzioni globali, con l'aggiunta che il metodo di classe può accedere alle proprietà dell'istanza di classe direttamente o tramite la parola chiave "this", nel caso in cui una variabile locale abbia lo stesso nome.

// The class declaration
class MyClass
{
  // A class method
  void DoSomething()
  {
    // The class properties can be accessed directly
    a *= 2;
    // The declaration of a local variable may hide class properties
    int b = 42;
    // In this case the class property have to be accessed explicitly
    this.b = b;
  }
  // Class properties
  int a;
  int b;
}

Metodi const

Le classi aggiungono un nuovo tipo di sovraccarico di funzione, ovvero il sovraccarico const. Quando si accede a un metodo di una classe tramite un riferimento o un handle di sola lettura, è possibile invocare solo i metodi contrassegnati come costanti. Quando il riferimento o l'handle è scrivibile, possono essere invocati entrambi i tipi, con la preferenza per la versione non const nel caso in cui entrambi corrispondano.

class CMyClass
{
  int method()       { a++; return a; }
  int method() const {      return a; }
  int a;
}
void Function()
{
   CMyClass o;
   const CMyClass @h = o;
   o.method(); // invokes the non-const version that increments the member a
   h.method(); // invokes the const version that doesn't increment the member a
}

Ereditarietà e polimorfismo

Lo script avanzato supporta l'ereditarietà singola, in cui una classe derivata eredita le proprietà e i metodi della sua classe base. L'ereditarietà multipla non è supportata, ma il polimorfismo è supportato dall'implementazione di interfacce e il riutilizzo del codice è garantito dall'inclusione di classi mixin.

Tutti i metodi della classe sono virtuali, quindi non è necessario specificarlo manualmente. Quando una classe derivata sovrascrive un'implementazione, può estendere l'implementazione originale chiamando specificamente il metodo della classe base utilizzando l'operatore di risoluzione dell'ambito. Quando si implementa il costruttore di una classe derivata, il costruttore della classe base viene richiamato utilizzando la parola chiave super. Se nessuno dei costruttori della classe base viene chiamato manualmente, il compilatore inserirà automaticamente una chiamata al costruttore predefinito all'inizio. Il distruttore della classe base sarà sempre chiamato dopo quello della classe derivata, quindi non è necessario farlo manualmente.

// A derived class
class MyDerived : MyBase
{
  // The default constructor
  MyDerived()
  {
    // Calling the non-default constructor of the base class
    super(10);
    b = 0;
  }
  // Overloading a virtual method
  void DoSomething()
  {
    // Call the base class' implementation
    MyBase::DoSomething();
    // Do something more
    b = a;
  }
  int b;
}

Una classe derivata da un'altra può essere implicitamente lanciata alla classe base. Lo stesso vale per le interfacce implementate da una classe. L'altra direzione richiede un cast esplicito, poiché non è noto in fase di compilazione se il cast è valido.

class A {}
class B : A {}
void Foo()
{
  A @handle_to_A;
  B @handle_to_B;
  @handle_to_A = A(); // OK
  @handle_to_A = B(); // OK. The reference will be implicitly cast to A@
  @handle_to_B = A(); // Not OK. This will give a compilation error
  @handle_to_B = B(); // OK
  @handle_to_A = handle_to_B; // OK. The reference will be implicitly cast to A@
  @handle_to_B = handle_to_A; // Not OK. This will give a compilation error
  @handle_to_B = cast<B>(handle_to_A); // OK. Though, the explicit cast will return null
                                       // if the object in handle_to_a is not really an
                                       // instance of B
}

Controllo extra con final, abstract e override

Una classe può essere contrassegnata come "finale" per impedirne l'ereditarietà. Si tratta di una funzione opzionale, utilizzata soprattutto nei progetti più grandi, dove ci sono molte classi e può essere difficile controllare manualmente l'uso corretto di tutte le classi. È anche possibile contrassegnare come "finali" singoli metodi di una classe, nel qual caso è ancora possibile ereditare dalla classe, ma il metodo finale non può essere sovrascritto.

Un'altra parola chiave che può essere usata per contrassegnare una classe è "abstract". Le classi astratte non possono essere istanziate, ma possono essere derivate. Le classi astratte sono usate più frequentemente quando si vuole creare una famiglia di classi derivando da una classe base comune, ma non si vuole che la classe base sia istanziata da sola. Attualmente non è possibile contrassegnare i metodi come astratti, quindi tutti i metodi devono avere un'implementazione anche per le classi astratte.

// A final class that cannot be inherited from
final class MyFinal
{
  MyFinal() {}
  void Method() {}
}
// A class with individual methods finalled
class MyPartiallyFinal
{
  // A final method that cannot be overridden
  void Method1() final {}
  // Normal method that can still be overridden by derived class
  void Method2() {}
}
// An abstract class
abstract class MyAbstractBase {}

Quando si deriva una classe, è possibile indicare al compilatore che un metodo è destinato a sovrascrivere un metodo della classe base ereditata. Quando questo viene fatto e non c'è un metodo corrispondente nella classe base, il compilatore emette un errore, perché sa che qualcosa non è stato implementato nel modo previsto. Questo è particolarmente utile per individuare errori in progetti di grandi dimensioni, dove una classe base può essere modificata, ma le classi derivate sono state dimenticate.

class MyBase
{
  void Method() {}
  void Method(int) {}
}
class MyDerived : MyBase
{
  void Method() override {}      // OK. The method is overriding a method in the base class
  void Method(float) override {} // Not OK. The method isn't overriding a method in base class
}

Membri protetti e privati classe

I membri della classe possono essere dichiarati come protetti o privati, per controllare da dove si può accedere ad essi. I membri protetti non possono essere accessibili dall'esterno della classe. I membri privati, inoltre, non sono accessibili alle classi derivate.

Questo può essere utile nei programmi di grandi dimensioni, quando si vogliono evitare errori di programmazione dovuti all'uso inappropriato di proprietà o metodi.

// A class with private members
class MyBase
{
  // The following are public members
  void PublicFunc()
  {
    // The class can access its own protected and private members
    ProtectedProp = 0; // OK
    ProtectedFunc();   // OK
    PrivateProp = 0;   // OK
    PrivateFunc();     // OK
  }   
  int PublicProp;
  // The following are protected members
  protected void ProtectedFunc() {}
  protected int ProtectedProp;
  // The following are private members
  private void PrivateFunc() {}
  private int PrivateProp;
}
class MyDerived : MyBase
{
  void Func()
  {
    // The derived class can access the protected members
    // of the base class but not the private members
    ProtectedProp = 1; // OK
    ProtectedFunc();   // OK
    PrivateProp = 1;   // Error
    PrivateFunc();     // Error 
  }
}
void GlobalFunc()
{
  MyBase obj;
  // Public members can be accessed normally
  obj.PublicProp = 0;  // OK
  obj.PublicFunc();    // OK
  // Accessing protected and private members will give a compiler error
  obj.ProtectedProp = 0; // Error
  obj.ProtectedFunc();   // Error
  obj.PrivateProp = 0;   // Error
  obj.PrivateFunc();     // Error
}

Sovraccarico operatori

È possibile definire cosa deve essere fatto quando un operatore viene utilizzato con una classe di script. Sebbene non sia necessario nella maggior parte degli script, può essere utile per migliorare la leggibilità del codice.

Questo si chiama sovraccarico dell'operatore e si ottiene implementando metodi specifici della classe. Il compilatore riconoscerà e utilizzerà questi

metodi quando compila espressioni che coinvolgono gli operatori sovraccaricati e la classe di script.

Operatori unari prefissi

op

opfunc

-

opNeg

~

opCom

++

opPreInc

--

opPreDec

Quando l'espressione op a viene compilata, il compilatore la riscrive come a.opfunc() e la compila al suo posto.

Operatori unari postfissi

op

opfunc

++

opPostInc

--

opPostDec

Quando l'espressione a op viene compilata, il compilatore la riscrive come a.opfunc() e la compila al suo posto.

Operatori di confronto

op

opfunc

==

opEquals

!=

opEquals

<

opCmp

<=

opCmp

>

opCmp

>=

opCmp

is

opEquals

!is

opEquals

L'espressione a == b viene riscritta come a.opEquals(b) e b.opEquals(a) e viene utilizzata la corrispondenza migliore. Il metodo != viene trattato in modo simile, tranne che per il fatto che il risultato viene negato. Il metodo opEquals deve essere implementato in modo da restituire un bool per essere considerato dal compilatore.

Gli operatori di confronto vengono riscritti come a.opCmp(b) op 0 e 0 op b.opCmp(a) e viene utilizzata la migliore corrispondenza. Il metodo opCmp deve essere implementato per restituire un int per essere considerato dal compilatore. Se l'argomento del metodo deve essere considerato più grande dell'oggetto, il metodo deve restituire un valore negativo. Se si suppone che siano uguali, il valore di ritorno deve essere 0.

Se viene effettuato un controllo di uguaglianza e il metodo opEquals non è disponibile, il compilatore cerca invece il metodo opCmp. Quindi, se il metodo opCmp è disponibile, non è necessario implementare il metodo opEquals, se non per motivi di ottimizzazione.

L'operatore di identità, is, si aspetta che opEquals prenda un handle, @, in modo che gli indirizzi possano essere confrontati per poter restituire se si tratta dello stesso oggetto, al contrario di due oggetti diversi che hanno lo stesso valore.

Operatori di assegnazione

op

opfunc

=

opAssign

+=

opAddAssign

-=

opSubAssign

*=

opMulAssign

/=

opDivAssign

%=

opModAssign

**=

opPowAssign

&=

opAndAssign

|=

opOrAssign

^=

opXorAssign

<<=

opShlAssign

>>=

opShrAssign

>>>=

opUShrAssign

Le espressioni di assegnazione a op b vengono riscritte come a.opfunc(b) e poi viene utilizzato il metodo di corrispondenza migliore. Un operatore di assegnazione può essere implementato, ad esempio, in questo modo:

obj &opAssign(const obj &inout other)
{
  // Do the proper assignment
  ...
  // Return a handle to self, so that multiple assignments can be chained
  return this;
}

Operatore di assegnazione generato automaticamente

Il compilatore genera automaticamente un opAssign per copiare il contenuto di un'istanza dello stesso tipo, nel caso in cui non sia dichiarato esplicitamente un metodo opAssign con un singolo parametro. L'opAssign generato copierà semplicemente ogni membro.

Se l'opAssign generato automaticamente non è desiderato, è possibile escluderlo esplicitamente segnalandolo come cancellato.

class MyClass
{
  MyClass &opAssign(const MyClass &inoutdelete;
}

Operatori binari

op

opfunc

opfunc_r

+

opAdd

opAdd_r

-

opSub

opSub_r

*

opMul

opMul_r

/

opDiv

opDiv_r

%

opMod

opMod_r

**

opPow

opPow_r

&

opAnd

opAnd_r

|

opOr

opOr_r

^

opXor

opXor_r

<<

opShl

opShl_r

>>

opShr

opShr_r

>>>

opUShr

opUShr_r

Le espressioni con operatori binari a op b verranno riscritte come a.opfunc(b) e b.opfunc_r(a) e verrà utilizzata la migliore corrispondenza.

Operatori indice

op

opfunc

[]

opIndex

Quando l'espressione a[i] viene compilata, il compilatore la riscrive come a.opIndex(i) e la compila al suo posto. Sono supportati anche argomenti multipli tra le parentesi.

L'operatore indice può essere formato in modo simile agli accessi alle proprietà. L'accessore get dovrebbe essere chiamato get_opIndex e avere un parametro per l'indicizzazione. L'accessore set dovrebbe chiamarsi set_opIndex e avere due parametri, il primo per l'indicizzazione e il secondo per il nuovo valore.

class MyObj
{
  float get_opIndex(int idx) const       { return 0; }
  void set_opIndex(int idx, float value) { }
}

Quando l'espressione a[i] viene utilizzata per recuperare il valore, il compilatore la riscrive come a.get_opIndex(i). Quando l'espressione viene usata per impostare il valore, il compilatore la riscriverà come a.set_opIndex(i, expr).

Operatore functor

op

opfunc

()

opCall

Quando viene compilata l'espressione expr(arglist) e expr valuta un oggetto, il compilatore la riscrive come expr.opCall(arglist) e la compila al suo posto.

Operatori di conversione di tipo

op

opfunc

type(expr)

constructor, opConv, opImplConv

cast<type>(expr)

opCast, opImplCast

Quando viene compilata l'espressione type(expr) e type non ha un costruttore di conversione che accetta un argomento con il tipo dell'espressione, il compilatore cercherà di riscriverla come expr.opConv(). Il compilatore sceglierà quindi l'opConv che restituisce il tipo desiderato.

Per le conversioni implicite, il compilatore cercherà un costruttore di conversione del tipo di destinazione che accetti un argomento corrispondente e non sia contrassegnato come esplicito. Se non ne trova uno, proverà a chiamare l'opImplConv sul tipo sorgente che restituisce il tipo di destinazione.

class MyObj
{
  double myValue;
  // Allow MyObj to be implicitly created from double
  MyObj(double v)            { myValue = v; }
  // Allow MyObj to be implicitly converted to double
  double opImplConv() const  return myValue; }
  // Allow MyObj to be created from int, but only explicitly
  MyObj(int v) explicit      { myValue = v; }
  // Allow MyObj to be converted to int, but only explicitly
  int opConv() const         { return int(myValue); }
}

Questo dovrebbe essere usato solo per le conversioni di valori e non per i cast di riferimenti. Cioè, ci si aspetta che i metodi restituiscano una nuova istanza del valore con il nuovo tipo.

Nota: durante la compilazione delle espressioni booleane nelle condizioni, il compilatore non utilizzerà il bool opImplConv sui tipi di riferimento, anche se il metodo della classe è implementato. Questo perché è ambiguo se sia l'handle a essere verificato o l'oggetto vero e proprio.

Se si desidera un cast di riferimento, cioè un handle di tipo diverso per la stessa istanza di oggetto, è necessario implementare il metodo opCast. Il compilatore cercherà di riscrivere un'espressione cast<tipo>(expr) come expr.opCast() e sceglierà l'overload opCast che restituisce un handle del tipo desiderato. Anche in questo caso si può implementare l'opImplCast, se il cast di riferimento può essere eseguito implicitamente dal compilatore.

class MyObjA
{
  MyObjB @objB;
  MyObjC @objC;
  MyObjB @opCast() { return objB; }
  MyObjC @opImplCast() { return objC; }
  const MyObjB @opCast() const { return objB; }
  const MyObjC @opImplCast() const return objC; }
}

Un esempio in cui gli overload dell'operatore opCast/opImplCast sono utili è quando si estende un tipo senza ereditarlo direttamente.

Accessi alle proprietà

Nota: l'applicazione può disattivare facoltativamente il supporto per gli accessi alle proprietà, quindi è necessario verificare il manuale dell'applicazione per determinare se questo è supportato o meno.

Spesso, quando si lavora con le proprietà, è necessario assicurarsi che venga seguita una logica specifica quando si accede ad esse. Un esempio potrebbe essere quello di inviare sempre una notifica quando una proprietà viene modificata, oppure di calcolare il valore della proprietà a partire da altre proprietà. Implementando i metodi di accesso alle proprietà, questo può essere implementato dalla classe stessa, rendendo più semplice l'accesso alle proprietà.

In script avanzato gli accessi alle proprietà sono dichiarati con la seguente sintassi:

class MyObj
{
  // A virtual property with accessors
  int prop
  {
    get const
    {
      // The actual value of the property could be stored
      // somewhere else, or even computed at access time.
      return realProp;
    }
    set
    {
      // The new value is stored in a hidden parameter appropriately called 'value'.
      realProp = value;
    }
  }
  // The actual value can be stored in a member or elsewhere.
  // It is actually possible to use the same name for the real property, if so is desired.
  private int realProp;
}

Dietro le quinte, il compilatore lo trasforma in due metodi con il nome della proprietà e i prefissi get_ e set_, e con il decoratore di funzioni 'property'. Il seguente genera il codice equivalente ed è perfettamente valido:

class MyObj
{
  int get_prop() const property { return realProp; }
  void set_prop(int value) property { realProp = value; }
  private int realProp;
}

Se si implementano gli accessi alle proprietà scrivendo esplicitamente i due metodi, è necessario assicurarsi che il tipo di ritorno dell'accesso get e il tipo di parametro dell'accesso set corrispondano, altrimenti il compilatore non saprà quale sia il tipo corretto da utilizzare.

Per le interfacce, la prima alternativa è di solito il modo preferito di dichiarare gli accessi alle proprietà, in quanto è abbastanza breve e facile da leggere.

interface IProp
{
  int prop { get constset; }
}

Si può anche omettere l'accessor get o set. Se si tralascia l'accessore set, la proprietà sarà di sola lettura. Se si tralascia l'accessore get, la proprietà sarà di sola scrittura.

Gli accessi alle proprietà possono essere implementati anche per le proprietà globali, che seguono le stesse regole, tranne per il fatto che le funzioni sono globali.

Una volta dichiarati gli accessi alle proprietà, è possibile accedervi come alle proprietà ordinarie e il compilatore espanderà automaticamente le espressioni alle chiamate di funzione appropriate, set_ o get_, a seconda di come la proprietà viene utilizzata nell'espressione.

void Func()
{
  MyObj obj;
  // Set the property value just like a normal property.
  // The compiler will convert this to a call to set_prop(10000).
  obj.prop = 10000;
  // Get the property value just a like a normal property.
  // The compiler will convert this to a call to get_prop().
  assert( obj.prop == 1000 );
}

Si noti che, poiché gli accessi alle proprietà sono in realtà una coppia di metodi piuttosto che un accesso diretto al valore, si applicano alcune restrizioni su come possono essere utilizzati nelle espressioni che ispezionano e mutano nella stessa operazione. Gli assegnamenti composti possono essere utilizzati sugli accessi alle proprietà se l'oggetto proprietario è di tipo reference, ma non se l'oggetto proprietario è di tipo value. Questo perché il compilatore deve essere in grado di garantire che l'oggetto rimanga in vita tra le due chiamate all'accessor get e all'accessor set.

Gli operatori di incremento e decremento non sono attualmente supportati.

In questi casi, l'espressione deve essere espansa in modo che le operazioni di lettura e scrittura vengano eseguite separatamente, ad esempio l'operatore increment deve essere riscritto come segue:

a++;     // will not work if a is a virtual property
a += 1;  // this is OK, as long as the owner of the virtual
         // property is a reference type or the property is global

Accessi a proprietà indicizzati

Gli accessi a proprietà possono essere utilizzati per emulare una singola proprietà o un array di proprietà a cui si accede tramite l'operatore indice. Gli accessi a proprietà per l'accesso indicizzato funzionano come gli accessi a proprietà ordinari, tranne che per il fatto che accettano un argomento indice. L'accessore get deve prendere l'argomento indice come unico argomento, mentre l'accessore set deve prendere l'argomento indice come primo argomento e il nuovo valore come secondo argomento.

string firstString;
string secondString;
// A global indexed get accessor
string get_stringArray(int idx) property
{
  switch(idx)
  {
  case 0: return firstString;
  case 1: return secondString;
  }
  return "";
}
// A global indexed set accessor
void set_stringArray(int idx, const string &in value) property
{
  switch(idx)
  {
  case 0: firstString = value; break;
  case 1: secondString = value; break;
  }
}
void main()
{
  // Setting the value of the indexed properties
  stringArray[0] = "Hello";
  stringArray[1] = "World";
  // Reading the value of the indexed properties
  print(StringArray[0] + " " + stringArray[1] + "\n");
}

Le assegnazioni composte attualmente non funzionano per le proprietà indicizzate.

 

  

Keyboard Navigation

F7 for caret browsing
Hold ALT and press letter

This Info: ALT+q
Page Header: ALT+h
Topic Header: ALT+t
Topic Body: ALT+b
Contents: ALT+c
Search: ALT+s
Exit Menu/Up: ESC