Condividi tramite


Classi e metodi parziali (Guida per programmatori C#)

È possibile suddividere la definizione di una classe, uno struct, un'interfaccia o un metodo tra 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 in file distinti ne consente l'uso simultaneo da parte di più programmatori.
  • È 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 una definizione di classe, usare il modificatore di parole chiave parziale . In pratica, ogni classe parziale viene in genere definita in un file separato, semplificando la gestione e l'espansione della classe nel tempo.

Nell'esempio seguente Employee viene illustrato come la classe può essere divisa tra due file: Employee_Part1.cs e Employee_Part2.cs.

// This is in Employee_Part1.cs
public partial class Employee
{
    public void DoWork()
    {
        Console.WriteLine("Employee is working.");
    }
}

// This is in Employee_Part2.cs
public partial class Employee
{
    public void GoToLunch()
    {
        Console.WriteLine("Employee is at lunch.");
    }
}

//Main program demonstrating the Employee class usage
public class Program
{
    public static void Main()
    {
        Employee emp = new Employee();
        emp.DoWork();
        emp.GoToLunch();
    }
}

// Expected Output:
// Employee is working.
// Employee is at lunch.

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 illustra che i tipi nidificati possono essere parziali, anche se non lo è il tipo all'interno del quale sono nidificati.

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 XML. Tuttavia, se entrambe le dichiarazioni di un membro parziale includono commenti, vengono inclusi solo i commenti del membro di implementazione.
  • 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 usano le definizioni di classe parziali è necessario rispettare 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 modificatore partial può essere specificato solo prima delle parole chiave class, struct o 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 in 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 riportate di seguito sono facoltative in una definizione di tipi parziali. Tuttavia, se presenti in una definizione parziale, queste devono essere 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 Coords classe vengono dichiarati in una definizione di classe parziale (Coords_Part1.cs) e il PrintCoords metodo viene dichiarato in un'altra definizione di classe parziale (Coords_Part2.cs). Questa separazione dimostra come le classi parziali possono essere divise tra più file per semplificare la gestibilità.

 // This is in Coords_Part1.cs
 public partial class Coords
 {
     private int x;
     private int y;

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

 // This is in Coords_Part2.cs
 public partial class Coords
 {
     public void PrintCoords()
     {
         Console.WriteLine("Coords: {0},{1}", x, y);
     }
 }

// Main program demonstrating the Coords class usage
 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() { }
}

Membri parziali

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

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

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

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

Qualsiasi metodo non conforme a tutte queste restrizioni, incluse le proprietà e gli indicizzatori, deve fornire un'implementazione. Tale implementazione potrebbe essere fornita da un generatore di origine. Le proprietà parziali non possono essere implementate usando proprietà implementate automaticamente. Il compilatore non può distinguere tra una proprietà implementata automaticamente e la dichiarazione dichiarante di una proprietà parziale.

A partire da C# 13, la dichiarazione di implementazione per una proprietà parziale può usare le proprietà supportate dal campo per definire la dichiarazione di implementazione. Una proprietà supportata da campi fornisce una sintassi concisa in cui la field parola chiave accede al campo sottostante sintetizzato dal compilatore per la proprietà . Ad esempio, è possibile scrivere quanto segue:

// in file1.cs
public partial class PropertyBag
{
    // Defining declaration
    public partial int MyProperty { get; set; }
}

// In file2.cs
public partial class PropertyBag
{
    // Defining declaration
    public partial int MyProperty { get => field; set; }
}

È possibile usare field nella get funzione di accesso o set o in entrambi.

Importante

La field parola chiave è una funzionalità di anteprima in C# 13. È necessario usare .NET 9 e impostare l'elemento <LangVersion> su preview nel file di progetto per usare la field parola chiave contestuale.

È consigliabile prestare attenzione usando la funzionalità parola field chiave in una classe con un campo denominato field. La nuova field parola chiave ombreggiate un campo denominato field nell'ambito di una funzione di accesso alle proprietà. È possibile modificare il nome della field variabile oppure usare il @ token per fare riferimento all'identificatore field come @field. Per altre informazioni, leggere la specifica della funzionalità per la field parola chiave .

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

  • Codice modello: il modello riserva un nome e una firma del metodo così che il codice generato possa chiamare il metodo. Questi metodi seguono le restrizioni che consentono allo sviluppatore di decidere se implementare o meno il metodo. Se il metodo non viene 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, il codice nella classe parziale può usare liberamente un metodo parziale, anche se non viene specificata l'implementazione. Non vengono generati errori in fase di compilazione o errori di runtime se il metodo viene chiamato ma non implementato.
  • Generatori di origine: i generatori di origine forniscono un'implementazione per i membri. Lo sviluppatore umano può aggiungere la dichiarazione del membro (spesso con attributi letti dal generatore di origine). Lo sviluppatore può scrivere codice che chiama questi membri. Il generatore di origine viene eseguito durante la compilazione e fornisce l'implementazione. In questo scenario, le restrizioni per i membri 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 membri parziali devono iniziare con la parola chiave contestuale partial.
  • Le firme dei membri parziali nelle due parti del tipo parziale devono corrispondere.
  • Il membro parziale può contenere i modificatori static e unsafe.
  • Il membro parziale può essere generico. I vincoli devono essere gli stessi nella dichiarazione del metodo di definizione e in quella di implementazione. I nomi dei parametri e dei parametri di tipo non devono essere uguali nella dichiarazione di implementazione e in quella di definizione.
  • È possibile creare un delegato di un metodo parziale definito e implementato, ma non di un metodo parziale per cui non esiste un'implementazione.

Specifiche del linguaggio C#

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

Vedi anche