Dela via


"mönsterbaserad användning" och "använda deklarationer"

Anteckning

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader fångas upp i de relevanta LDM-anteckningarna (Language Design Meeting).

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-problem: https://github.com/dotnet/csharplang/issues/114

Sammanfattning

Språket lägger till två nya funktioner runt using-instruktionen för att göra resurshanteringen enklare: using bör känna igen ett disponibelt mönster utöver IDisposable och lägga till en using-deklaration på språket.

Motivation

using-instruktionen är ett effektivt verktyg för resurshantering idag, men det kräver en hel del ceremonier. Metoder som har flera resurser att hantera kan bli syntaktiskt överväldigade med en serie using-satser. Den här syntaxbördan räcker för att de flesta riktlinjer för kodningsformat uttryckligen har ett undantag kring klammerparenteser för det här scenariot.

Den using deklarationen tar bort mycket av ceremonin här och får C# i nivå med andra språk som innehåller resurshanteringsblock. Med den mönsterbaserade using kan utvecklare dessutom utöka den uppsättning typer som kan delta här. I många fall tas behovet bort att skapa wrapper-typer som bara finns för att låta värden användas i en using-instruktion.

Tillsammans gör dessa funktioner det möjligt för utvecklare att förenkla och utöka scenarier där using kan användas.

Detaljerad design

använda deklaration

Språket gör att using kan läggas till i en lokal variabeldeklaration. En sådan deklaration har samma effekt som att deklarera variabeln i en using-instruktion på samma plats.

if (...) 
{ 
   using FileStream f = new FileStream(@"C:\source\using.md");
   // statements
}

// Equivalent to 
if (...) 
{ 
   using (FileStream f = new FileStream(@"C:\source\using.md")) 
   {
    // statements
   }
}

Livslängden för en using lokal utökas tills slutet av det område som den deklareras i. De lokala using-variablerna kommer sedan att avvecklas i motsatt ordning mot den ordning de deklarerades.

{ 
    using var f1 = new FileStream("...");
    using var f2 = new FileStream("...");
    using var f3 = new FileStream("...");
    ...
    // Dispose f3
    // Dispose f2 
    // Dispose f1
}

Det finns inga begränsningar kring goto, eller någon annan kontrollflödeskonstruktion inför en using-deklaration. I stället fungerar koden precis som för motsvarande using-instruktion:

{
    using var f1 = new FileStream("...");
  target:
    using var f2 = new FileStream("...");
    if (someCondition) 
    {
        // Causes f2 to be disposed but has no effect on f1
        goto target;
    }
}

En lokal som deklareras i en using lokal deklaration är implicit skrivskyddad. Detta matchar beteendet för lokala variabler som deklareras i en using-sats.

Grammatiken för språket för using-deklarationer kommer att vara följande:

local-using-declaration:
  'using' type using-declarators

using-declarators:
  using-declarator
  using-declarators , using-declarator
  
using-declarator:
  identifier = expression

Begränsningar kring using deklaration:

  • Får inte visas direkt i en case etikett utan måste i stället finnas inom ett block inuti etiketten case.
  • Får inte visas som en del av en out variabeldeklaration.
  • Måste ha en initialiserare för varje deklarator.
  • Den lokala typen måste implicit konverteras till IDisposable eller uppfylla using mönster.

mönsterbaserad användning

Språket lägger till begreppet disponibelt mönster för ref struct typer: det är en ref struct som har en tillgänglig Dispose instansmetod. Typer som passar engångsmönstret kan delta i en using-instruktion eller deklaration utan att behöva implementera IDisposable.

ref struct Resource
{ 
    public void Dispose() { ... }
}

using (var r = new Resource())
{
    // statements
}

Detta gör det möjligt för utvecklare att utnyttja using för ref struct typer. Dessa typer kan inte implementera gränssnitt i C# 8 och kan därför inte delta i using-instruktioner.

Samma begränsningar från en traditionell using-instruktion gäller även här: lokala variabler som deklareras i using är skrivskyddade, ett null värde kommer inte att orsaka att ett undantag kastas, osv. Kodgenereringen kommer endast att vara annorlunda eftersom det inte kommer att finnas någon typkonvertering till IDisposable innan Dispose anropas.

{
	  Resource r = new Resource();
	  try {
		    // statements
	  }
	  finally {
		    if (r != null) r.Dispose();
	  }
}

För att passa engångsmönstret måste metoden Dispose vara en tillgänglig instansmedlem, parameterlös och ha en void returtyp. Det kan inte vara en tilläggsmetod.

Överväganden

Inget av dessa överväganden har implementerats i C# 8

skiftlägesetiketter utan block

En using declaration är olaglig direkt i en case etikett på grund av komplikationer kring dess faktiska livslängd. En möjlig lösning är att helt enkelt ge den samma livslängd som en out var på samma plats. Det beslutades att den extra komplexiteten i funktionsimplementeringen och den enkla lösningen (lägg bara till ett block till case-etiketten) inte motiverade att ta den här vägen.

Framtida expansioner

fasta lokala inställningar

En fixed-instruktion har alla egenskaper hos using-instruktioner som motiverade möjligheten att ha using lokala variabler. Du bör även överväga att utöka den här funktionen till att även fixed lokalbefolkningen. Reglerna för livslängd och ordning bör gälla lika bra för using och fixed här.