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 chiaveclass
,struct
ointerface
. - 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à.