Implementare classi usando classi parziali
È possibile suddividere la definizione di una classe o di un metodo su due o più file di origine. Ogni file di origine contiene una sezione della definizione del tipo o del metodo e tutte le parti vengono combinate quando l'applicazione viene compilata.
Classi parziali
La suddivisione di una definizione di 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 quando crea Windows Form, codice wrapper del servizio 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 di Employee seguente 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 nello spazio dei nomi . Tutte le parti devono usare la parola chiave partial. Tutte le parti devono essere disponibili in fase di compilazione per formare il tipo finale. Tutte le parti devono avere la stessa accessibilità, ad esempio public, privatee così via.
Se una parte viene dichiarata astratta, l'intero tipo viene considerato astratto. Se una parte è dichiarata sealed, l'intero tipo viene considerato sealed. Se una parte dichiara un tipo di base, l'intero tipo eredita tale classe.
Tutte le parti che specificano una classe di base devono accettare, ma le parti che omettono una classe base ereditano ancora il tipo di base. Le parti possono specificare interfacce di base diverse e il tipo finale implementa tutte le interfacce elencate da tutte le dichiarazioni parziali. Tutti i membri di classe, struct o interfaccia dichiarati in una definizione parziale sono disponibili per tutte le altre parti. Il tipo finale è la combinazione di tutte le parti in fase di compilazione.
Nota
Il modificatore partial non è disponibile nelle dichiarazioni di delega 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 tipo parziale 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 { }
Di seguito viene eseguito il merge da tutte le definizioni di tipo parziale:
- Commenti XML. Tuttavia, se entrambe le dichiarazioni di un membro parziale includono commenti, vengono inclusi solo i commenti del membro di implementazione.
- Interfacce
- Attributi dei parametri di tipo generico
- attributi di classe
- 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 sulle definizioni di classi parziali
Quando si utilizzano definizioni di classi parziali, è necessario seguire diverse regole:
Tutte le definizioni di tipo parziale destinate a essere parti dello stesso tipo devono essere modificate con parziale. Ad esempio, le dichiarazioni di classe seguenti generano un errore:
public partial class A { } //public class A { } // Error, must also be marked partialIl modificatore parziale può essere visualizzato solo immediatamente prima della classe, dello struct o dell'interfaccia della parola chiave.
I tipi parziali annidati sono consentiti nelle definizioni di tipo parziale, come illustrato nell'esempio seguente:
partial class ClassWithNestedClass { partial class NestedClass { } } partial class ClassWithNestedClass { partial class NestedClass { } }Tutte le definizioni di tipo parziale destinate a essere parti dello stesso tipo devono essere definite nello stesso assembly e nello stesso modulo (.exe o .dll file). Le definizioni parziali non possono estendersi su più moduli.
Il nome della classe e i parametri di tipo generico devono corrispondere a tutte le definizioni di tipo parziale. I tipi generici possono essere parziali. Ogni dichiarazione parziale deve usare gli stessi nomi di parametro nello stesso ordine.
Le parole chiave seguenti in una definizione di tipo parziale sono facoltative, ma se presenti in una definizione di tipo parziale, è necessario specificare lo stesso valore in un'altra definizione parziale per lo stesso tipo:
- pubblico
- privato
- protetto
- interno
- astratto
- sigillato
- classe base
- nuovo modificatore (parti annidate)
- vincoli generici
Implementare classi parziali
Nell'esempio seguente i campi e il costruttore della classe Coords vengono dichiarati in una definizione di classe parziale (Coords_Part1.cs) e il metodo PrintCoords 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
Membri parziali
Una classe parziale può 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 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 accesso privato per impostazione predefinita.
Il tipo restituito è
void.Nessuno dei parametri ha il modificatore
out.La dichiarazione del metodo non può includere uno dei modificatori seguenti:
- virtuale
- prevalere
- sigillato
- Nuovo
- Extern
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 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 parola chiave field accede al campo sottostante sintetizzato dal compilatore per la proprietà . Ad esempio, è possibile scrivere il codice seguente:
// 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 funzione di accesso get o set oppure in entrambi i casi.
Importante
La parola chiave field è una funzionalità di anteprima in C# 13. È necessario usare .NET 9 e impostare l'elemento <LangVersion> in anteprima nel file di progetto per usare la parola chiave contestuale field.
È consigliabile prestare attenzione usando la funzionalità di parola chiave field in una classe con un campo denominato field. La nuova parola chiave field ombreggiate un campo denominato field nell'ambito di una funzione di accesso a una proprietà. È possibile modificare il nome della variabile di campo oppure usare il token @ per fare riferimento all'identificatore di campo come @field.
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. 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 i risultati che si verificherebbero dalla valutazione degli 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 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 parziale.
Le firme dei membri parziali in entrambe le parti del tipo parziale devono corrispondere.
Un membro parziale può avere modificatori statici e non sicuri.
Il membro parziale può essere generico. 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.