Classi e metodi parziali (Guida per programmatori C#)

È possibile suddividere la definizione di una classe, uno struct, un'interfaccia o un metodo su due o più file di origine. Ogni file di origine contiene una sezione della definizione di tipo o metodo e tutte le parti vengono combinate al momento della compilazione dell'applicazione.

Classi parziali

La suddivisione della definizione di una classe è consigliabile in diverse situazioni:

  • La dichiarazione di una classe su file separati consente a più programmatori di usarla contemporaneamente.
  • È possibile aggiungere codice alla classe senza dover ricreare il file di origine che include l'origine generata automaticamente. Visual Studio usa questo approccio per la creazione di Windows Form, codice wrapper di servizi Web e così via. È possibile creare codice che usa queste classi senza dover modificare il file creato da Visual Studio.
  • I generatori di origine possono generare funzionalità aggiuntive in una classe.

Per suddividere la definizione di una classe, usare il modificatore della parola chiave partial, come illustrato di seguito:

public partial class Employee
{
    public void DoWork()
    {
    }
}

public partial class Employee
{
    public void GoToLunch()
    {
    }
}

La parola chiave partial indica che è possibile definire altre parti della classe, dello struct o dell'interfaccia nello spazio dei nomi. Tutte le parti devono usare la parola chiave partial ed essere disponibili in fase di compilazione in modo da formare il tipo finale. Tutte le parti devono anche avere lo stesso livello di accessibilità, ad esempio public, private e così via.

Se una parte viene dichiarata come astratta, l'intero tipo verrà considerato astratto. Se una parte viene dichiarata come sealed, l'intero tipo verrà considerato sealed. Se una parte dichiara un tipo base, l'intero tipo eredita la classe.

Tutte le parti che specificano una classe base devono concordare, tuttavia le parti che omettono una classe base ereditano comunque il tipo base. Le parti possono specificare interfacce di base differenti e il tipo finale implementa tutte le interfacce elencate da tutte le dichiarazioni parziali. Tutti i membri di classe, struttura o interfaccia dichiarati in una definizione parziale sono disponibili per tutte le altre parti. Il tipo finale rappresenta la combinazione di tutte le parti in fase di compilazione.

Nota

Il modificatore partial non è disponibile per le dichiarazioni di delegato o di enumerazione.

L'esempio seguente mostra che i tipi annidati possono essere parziali, anche se il tipo in cui sono annidati non è parziale.

class Container
{
    partial class Nested
    {
        void Test() { }
    }

    partial class Nested
    {
        void Test2() { }
    }
}

In fase di compilazione gli attributi delle definizioni di tipi parziali vengono uniti. Si considerino ad esempio le dichiarazioni seguenti:

[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

Sono equivalenti alle dichiarazioni seguenti:

[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

Gli elementi seguenti vengono uniti da tutte le definizioni di tipi parziali:

  • Commenti in XML
  • interfaces
  • attributi di parametri di tipo generico
  • attributi class
  • membri

Si considerino ad esempio le dichiarazioni seguenti:

partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }

Sono equivalenti alle dichiarazioni seguenti:

class Earth : Planet, IRotate, IRevolve { }

Restrizioni

Quando si utilizzano definizioni di classi parziali, è necessario seguire diverse regole:

  • Tutte le definizioni di tipi parziali destinate a essere parti dello stesso tipo devono essere modificate con partial. Ad esempio, le dichiarazioni di classe seguenti generano un errore:
    public partial class A { }
    //public class A { }  // Error, must also be marked partial
    
  • Il partial modificatore può essere visualizzato solo immediatamente prima della parola chiave class, structo interface.
  • I tipi parziali annidati sono consentiti nelle definizioni di tipi parziali, come illustrato nell'esempio seguente:
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
  • Tutte le definizioni di tipi parziali destinate a essere parti dello stesso tipo devono essere definite nello stesso assembly e nello stesso modulo (file con estensione exe o dll). Le definizioni parziali non possono estendersi su più moduli.
  • Il nome della classe e i parametri di tipo generico devono corrispondere in tutte le definizioni di tipi parziali. I tipi generici possono essere parziali. In ogni dichiarazione parziale è necessario usare gli stessi nomi di parametri nello stesso ordine.
  • Le parole chiave seguenti in una definizione di tipo parziale sono facoltative, ma se presenti in una definizione di tipo parziale, non possono essere in conflitto con le parole chiave specificate in un'altra definizione parziale per lo stesso tipo:

Per altre informazioni, vedere Vincoli sui parametri di tipo.

Esempi

Nell'esempio seguente i campi e il costruttore della classe Coords vengono dichiarati in una definizione parziale di classe, mentre il membro PrintCoords viene dichiarato in un'altra definizione parziale di classe.

public partial class Coords
{
    private int x;
    private int y;

    public Coords(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

public partial class Coords
{
    public void PrintCoords()
    {
        Console.WriteLine("Coords: {0},{1}", x, y);
    }
}

class TestCoords
{
    static void Main()
    {
        Coords myCoords = new Coords(10, 15);
        myCoords.PrintCoords();

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: Coords: 10,15

L'esempio seguente dimostra che è anche possibile sviluppare struct e interfacce parziali.

partial interface ITest
{
    void Interface_Test();
}

partial interface ITest
{
    void Interface_Test2();
}

partial struct S1
{
    void Struct_Test() { }
}

partial struct S1
{
    void Struct_Test2() { }
}

Metodi parziali

Una classe o uno struct parziale può contenere un metodo parziale. Una parte della classe contiene la firma del metodo. Un'implementazione può essere definita nella stessa parte o in un'altra parte.

Un'implementazione non è necessaria per un metodo parziale quando la firma rispetta le regole seguenti:

  • La dichiarazione non include modificatori di accesso. Il metodo ha private accesso per impostazione predefinita.
  • Il tipo restituito è void.
  • Nessuno dei parametri ha il out modificatore.
  • La dichiarazione del metodo non può includere uno dei modificatori seguenti:

Il metodo e tutte le chiamate al metodo vengono rimosse in fase di compilazione quando non è presente alcuna implementazione.

Qualsiasi metodo che non è conforme a tutte le restrizioni (ad esempio, il metodo public virtual partial void), deve fornire un'implementazione. Tale implementazione potrebbe essere fornita da un generatore di origine.

I metodi parziali consentono all'implementatore di una parte di una classe di dichiarare un metodo. L'implementatore di un'altra parte della classe può definire tale metodo. Esistono due scenari in cui questa separazione è utile: i modelli che generano codice boilerplate e generatori di origine.

  • Codice modello: il modello riserva un nome e una firma del metodo in modo che il codice generato possa chiamare il metodo . Questi metodi seguono le restrizioni che consentono a uno sviluppatore di decidere se implementare il metodo . Se il metodo non è implementato, il compilatore rimuove la firma del metodo e tutte le chiamate al metodo. Le chiamate al metodo, inclusi eventuali risultati che derivassero dalla valutazione di argomenti nelle chiamate, non hanno alcun effetto in fase di esecuzione. Pertanto, qualsiasi codice nella classe parziale può usare liberamente un metodo parziale, anche se l'implementazione non viene fornita. Se il metodo viene chiamato ma non implementato, non viene generato alcun errore in fase di compilazione o di runtime.
  • Generatori di origine: i generatori di origine forniscono un'implementazione per i metodi. Lo sviluppatore umano può aggiungere la dichiarazione del metodo (spesso con attributi letti dal generatore di origine). Lo sviluppatore può scrivere codice che chiama questi metodi. Il generatore di origine viene eseguito durante la compilazione e fornisce l'implementazione. In questo scenario, le restrizioni per i metodi parziali che potrebbero non essere implementate spesso non vengono seguite.
// Definition in file1.cs
partial void OnNameChanged();

// Implementation in file2.cs
partial void OnNameChanged()
{
  // method body
}
  • Le dichiarazioni di metodi parziali devono iniziare con la parola chiave contestuale parziale.
  • Le firme del metodo parziale in entrambe le parti del tipo parziale devono corrispondere.
  • I metodi parziali possono contenere modificatori static e unsafe.
  • I metodi parziali possono essere generici. I vincoli devono essere gli stessi nella dichiarazione del metodo di definizione e implementazione. I nomi dei parametri di parametro e di tipo non devono essere uguali nella dichiarazione di implementazione come nell'elemento che ne definisce uno.
  • È possibile creare un delegato a un metodo parziale definito e implementato, ma non a un metodo parziale che non dispone di un'implementazione.

Specifiche del linguaggio C#

Per altre informazioni, vedere Tipi parziali e Metodi parziali nella specifica del linguaggio C#. La specifica del linguaggio costituisce il riferimento ufficiale principale per la sintassi e l'uso di C#. Le funzionalità aggiuntive per i metodi parziali sono definite nella specifica della funzionalità.

Vedi anche