Condividi tramite


Estensione di metodi parziali

Nota

Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.

Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono acquisite nelle pertinenti note del language design meeting (LDM) .

Puoi saperne di più sul processo di adozione delle specifiche parziali di funzionalità nello standard linguistico C# nell'articolo sulle specifiche di .

Problema del campione: https://github.com/dotnet/csharplang/issues/3301

Sommario

Questa proposta mira a rimuovere tutte le restrizioni relative alle firme dei metodi partial in C#. L'obiettivo è quello di espandere il set di scenari in cui questi metodi possono usare i generatori di origine, nonché un modulo di dichiarazione più generale per i metodi C#.

Vedere anche la specifica originale dei metodi parziali (§15.6.9).

Motivazione

C# ha un supporto limitato per gli sviluppatori che suddividono i metodi in dichiarazioni e definizioni/implementazioni.

partial class C
{
    // The declaration of C.M
    partial void M(string message);
}

partial class C
{
    // The definition of C.M
    partial void M(string message) => Console.WriteLine(message);
}

Un comportamento di partial metodi consiste nel fatto che quando la definizione è assente, il linguaggio cancellerà semplicemente tutte le chiamate al metodo partial. Essenzialmente si comporta come una chiamata a un metodo [Conditional] in cui la condizione è stata valutata su false.

partial class D
{
    partial void M(string message);

    void Example()
    {
        M(GetIt()); // Call to M and GetIt erased at compile time
    }

    string GetIt() => "Hello World";
}

Il motivo principale per questa funzionalità era la generazione di codice sorgente sotto forma di codice generato dal designer. Gli utenti modificavano costantemente il codice generato perché volevano associare alcuni aspetti del codice generato. In particolare, parti del processo di avvio di Windows Forms, dopo l'inizializzazione dei componenti.

La modifica del codice generato è soggetta a errori perché qualsiasi azione che causava la rigenerazione del codice da parte del designer causerebbe l'eliminazione della modifica dell'utente. La caratteristica del metodo partial ha facilitato questa tensione perché ha permesso ai progettisti di emettere ganci sotto forma di metodi partial.

I progettisti possono emettere hook come partial void OnComponentInit() e gli sviluppatori possono definire dichiarazioni corrispondenti o meno per essi. In entrambi i casi, il codice generato sarebbe compilato e gli sviluppatori interessati al processo potevano integrarsi secondo le necessità.

Ciò significa che i metodi parziali hanno diverse restrizioni:

  1. Deve avere un tipo di ritorno void.
  2. Impossibile avere parametri out.
  3. Non può avere alcun tipo di accessibilità (in modo implicito private).

Queste restrizioni esistono perché il linguaggio deve essere in grado di generare codice quando il sito di chiamata viene cancellato. Dato che possono essere cancellati, private è l'unico livello di accessibilità possibile perché il membro non può essere esposto nei metadati dell'assembly. Queste restrizioni servono anche a limitare l'insieme di scenari in cui i metodi partial possono essere applicati.

La proposta è quella di rimuovere tutte le restrizioni esistenti relative ai metodi partial. Essenzialmente consentire loro di avere parametri out, tipi restituiti non void o qualsiasi tipo di accessibilità. Tali dichiarazioni di partial avrebbero quindi il requisito aggiunto che deve esistere una definizione. Ciò significa che la lingua non deve considerare l'impatto della cancellazione dei siti di chiamata.

In questo modo si espanderebbe il set di scenari dei generatori a cui i metodi partial potrebbero partecipare, collegandosi perfettamente alla nostra funzionalità di generatori di origine. Ad esempio, è possibile definire un'espressione regolare usando il modello seguente:

[RegexGenerated("(dog|cat|fish)")]
partial bool IsPetMatch(string input);

Questo offre allo sviluppatore un semplice modo dichiarativo per abilitare i generatori, oltre a fornire ai generatori un insieme molto semplice di dichiarazioni da esaminare nel codice sorgente per pilotare l'output generato.

Confronta con la difficoltà che un generatore incontrerebbe nel collegare il frammento di codice seguente.

var regex = new RegularExpression("(dog|cat|fish)");
if (regex.IsMatch(someInput))
{

}

Poiché il compilatore non consente ai generatori di modificare il codice integrato con questo schema, sarebbe praticamente impossibile per i generatori. Sarebbe necessario ricorrere alla riflessione nell'implementazione IsMatch o chiedere agli utenti di modificare i punti di invocazione in un nuovo metodo o effettuare il refactoring dell'espressione regolare per passare la stringa letterale come argomento. È piuttosto disordinato.

Progettazione dettagliata

Il linguaggio cambierà per consentire l'annotazione dei metodi partial con un modificatore esplicito di accessibilità. Ciò significa che possono essere etichettati come private, publice così via...

Quando un metodo partial dispone di un modificatore di accessibilità esplicito, il linguaggio richiederà che la dichiarazione abbia una definizione corrispondente anche quando l'accessibilità è private:

partial class C
{
    // Okay because no definition is required here
    partial void M1();

    // Okay because M2 has a definition
    private partial void M2();

    // Error: partial method M3 must have a definition
    private partial void M3();
}

partial class C
{
    private partial void M2() { }
}

Il linguaggio rimuoverà inoltre tutte le restrizioni relative a ciò che può essere visualizzato in un metodo partial che ha un'accessibilità esplicita. Tali dichiarazioni possono contenere tipi di ritorno non void, parametri out, modificatore extern e così via... Queste firme avranno l'espressività completa del linguaggio C#.

partial class D
{
    // Okay
    internal partial bool TryParse(string s, out int i); 
}

partial class D
{
    internal partial bool TryParse(string s, out int i) { ... }
}

Ciò consente in modo esplicito ai metodi di partial di partecipare alle implementazioni di overrides e interface:

interface IStudent
{
    string GetName();
}

partial class C : IStudent
{
    public virtual partial string GetName(); 
}

partial class C
{
    public virtual partial string GetName() => "Jarde";
}

Il compilatore modificherà l'errore generato quando un metodo partial contiene un elemento non valido per indicare essenzialmente:

Impossibile usare ref su un metodo partial che non dispone di un'accessibilità specificata

Ciò consentirà agli sviluppatori di puntare nella giusta direzione quando si usa questa funzionalità.

Restrizioni:

  • partial dichiarazioni con accessibilità esplicita deve avere una definizione
  • partial dichiarazioni e firme di definizione devono corrispondere su tutti i modificatori di metodo e parametro. Gli unici aspetti che possono differire sono i nomi dei parametri e gli elenchi di attributi (questo non è nuovo ma piuttosto un requisito esistente dei metodi di partial).

Domande

parziale su tutti i membri

Dato che stiamo espandendo partial per essere più compatibile con i generatori di codice sorgente, dovremmo espanderlo anche per funzionare con tutti i membri della classe? Ad esempio, dovremmo essere in grado di dichiarare costruttori partial, operatori e così via...

Risoluzione L'idea è valida, ma nella pianificazione di C# 9 in questo momento si sta tentando di evitare una proliferazione inutile delle funzionalità. Si vuole affrontare il problema immediato di adattare la funzionalità per lavorare con moderni generatori di codice sorgente.

L'estensione di partial per supportare altri componenti verrà considerata per la versione C# 10. Sembra probabile che si consideri questa estensione. Questo rimane una proposta attiva, ma non è ancora stata implementata.

Usare l'astratto anziché il parziale

L'elemento fondamentale di questa proposta consiste essenzialmente nel garantire che una dichiarazione abbia una definizione/implementazione corrispondente. Considerando che dovremmo usare abstract poiché è già una parola chiave del linguaggio che spinge lo sviluppatore a considerare di avere un'implementazione?

risoluzione si è verificato un dibattito integro su questo, ma alla fine è stato deciso contro. Sì, i requisiti sono familiari, ma i concetti sono notevolmente diversi. Potrebbe facilmente guidare lo sviluppatore a credere che stesse creando slot virtuali anche se non lo stava facendo.