Dela via


Metodparametrar

Som standard skickas argument i C# till funktioner efter värde. Det innebär att en kopia av variabeln skickas till metoden. För värdetyper (struct) skickas en kopia av värdet till metoden. För referenstyper (class) skickas en kopia av referensen till metoden. Med parametermodifierare kan du skicka argument med referens. Följande begrepp hjälper dig att förstå dessa skillnader och hur du använder parametermodifierarna:

  • Skicka efter värde innebär att en kopia av variabeln skickas till metoden.
  • Skicka med referens innebär att du skickar åtkomst till variabeln till -metoden.
  • En variabel av en referenstyp innehåller en referens till dess data.
  • En variabel av en värdetyp innehåller dess data direkt.

Eftersom en struct är en värdetyp tar metoden emot och fungerar på en kopia av struct-argumentet när du skickar en struct efter värde till en metod. Metoden har ingen åtkomst till den ursprungliga structen i anropande metod och kan därför inte ändra den på något sätt. Metoden kan bara ändra kopian.

En klassinstans är en referenstyp som inte är en värdetyp. När en referenstyp skickas med ett värde till en metod tar metoden emot en kopia av referensen till klassinstansen. Båda variablerna refererar till samma objekt. Parametern är en kopia av referensen. Den anropade metoden kan inte omtilldela instansen i anropande metod. Den anropade metoden kan dock använda kopian av referensen för att komma åt instansmedlemmarna. Om den anropade metoden ändrar en instansmedlem ser anropande metoden också dessa ändringar eftersom den refererar till samma instans.

Utdata från följande exempel illustrerar skillnaden. Metoden ClassTaker ändrar värdet willIChange för fältet eftersom metoden använder adressen i parametern för att hitta det angivna fältet i klassinstansen. Fältet willIChange i structen i anropande metod ändras inte från anrop StructTaker eftersom argumentets värde är en kopia av själva structen, inte en kopia av dess adress. StructTaker ändrar kopian och kopian går förlorad när anropet till StructTaker har slutförts.

class TheClass
{
    public string? willIChange;
}

struct TheStruct
{
    public string willIChange;
}

class TestClassAndStruct
{
    static void ClassTaker(TheClass c)
    {
        c.willIChange = "Changed";
    }

    static void StructTaker(TheStruct s)
    {
        s.willIChange = "Changed";
    }

    public static void Main()
    {
        TheClass testClass = new TheClass();
        TheStruct testStruct = new TheStruct();

        testClass.willIChange = "Not Changed";
        testStruct.willIChange = "Not Changed";

        ClassTaker(testClass);
        StructTaker(testStruct);

        Console.WriteLine("Class field = {0}", testClass.willIChange);
        Console.WriteLine("Struct field = {0}", testStruct.willIChange);
    }
}
/* Output:
    Class field = Changed
    Struct field = Not Changed
*/

Kombinationer av parametertyp och argumentläge

Hur ett argument skickas och om det är en referenstyp eller värdetyp styr vilka ändringar som görs i argumentet som visas från anroparen:

  • När du skickar en värdetyp efter värde:
    • Om metoden tilldelar parametern för att referera till ett annat objekt visas inte dessa ändringar från anroparen.
    • Om metoden ändrar tillståndet för det objekt som anges av parametern visas inte dessa ändringar från anroparen.
  • När du skickar en referenstyp efter värde:
    • Om metoden tilldelar parametern för att referera till ett annat objekt visas inte dessa ändringar från anroparen.
    • Om metoden ändrar tillståndet för det objekt som anges av parametern visas dessa ändringar från anroparen.
  • När du skickar en värdetyp efter referens:
    • Om metoden tilldelar parametern att referera till ett annat objekt med , ref =visas inte dessa ändringar från anroparen.
    • Om metoden ändrar tillståndet för det objekt som anges av parametern visas dessa ändringar från anroparen.
  • När du skickar en referenstyp efter referens:
    • Om metoden tilldelar parametern för att referera till ett annat objekt visas ändringarna från anroparen.
    • Om metoden ändrar tillståndet för det objekt som anges av parametern visas dessa ändringar från anroparen.

Genom att skicka en referenstyp med referens kan den anropade metoden ersätta det objekt som referensparametern refererar till i anroparen. Lagringsplatsen för objektet skickas till metoden som värdet för referensparametern. Om du ändrar värdet på lagringsplatsen för parametern (för att peka på ett nytt objekt) ändrar du även lagringsplatsen som anroparen refererar till. I följande exempel skickas en instans av en referenstyp som en ref parameter.

class Product
{
    public Product(string name, int newID)
    {
        ItemName = name;
        ItemID = newID;
    }

    public string ItemName { get; set; }
    public int ItemID { get; set; }
}

private static void ChangeByReference(ref Product itemRef)
{
    // Change the address that is stored in the itemRef parameter.
    itemRef = new Product("Stapler", 12345);
}

private static void ModifyProductsByReference()
{
    // Declare an instance of Product and display its initial values.
    Product item = new Product("Fasteners", 54321);
    System.Console.WriteLine("Original values in Main.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);

    // Pass the product instance to ChangeByReference.
    ChangeByReference(ref item);
    System.Console.WriteLine("Calling method.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);
}

// This method displays the following output:
// Original values in Main.  Name: Fasteners, ID: 54321
// Calling method.  Name: Stapler, ID: 12345

Säker kontext för referenser och värden

Metoder kan lagra parametrarnas värden i fält. När parametrar skickas av värde är det vanligtvis säkert. Värden kopieras och referenstyper kan nås när de lagras i ett fält. Om parametrar skickas med referens på ett säkert sätt måste kompilatorn definiera när det är säkert att tilldela en referens till en ny variabel. För varje uttryck definierar kompilatorn en säker kontext som begränsar åtkomsten till ett uttryck eller en variabel. Kompilatorn använder två omfång: safe-context och ref-safe-context.

  • Safe-context definierar omfånget där alla uttryck kan nås på ett säkert sätt.
  • Ref-safe-context definierar omfånget där en referens till alla uttryck kan nås eller ändras på ett säkert sätt.

Informellt kan du se dessa omfång som en mekanism för att se till att koden aldrig kommer åt eller ändrar en referens som inte längre är giltig. En referens är giltig så länge den refererar till ett giltigt objekt eller en fjäder. Safe-context definierar när en variabel kan tilldelas eller omtilldelas. Ref-safe-context definierar när en variabel kan tilldelas eller omtilldelas. Tilldelning tilldelar en variabel till ett nytt värde. referenstilldelning tilldelar variabeln för att referera till en annan lagringsplats.

Referensparametrar

Du tillämpar någon av följande modifierare på en parameterdeklaration för att skicka argument efter referens i stället för efter värde:

  • ref: Argumentet måste initieras innan metoden anropas. Metoden kan tilldela parametern ett nytt värde, men krävs inte för att göra det.
  • out: Anropande metod krävs inte för att initiera argumentet innan metoden anropas. Metoden måste tilldela parametern ett värde.
  • ref readonly: Argumentet måste initieras innan metoden anropas. Metoden kan inte tilldela parametern ett nytt värde.
  • in: Argumentet måste initieras innan metoden anropas. Metoden kan inte tilldela parametern ett nytt värde. Kompilatorn kan skapa en tillfällig variabel för att lagra en kopia av argumentet till in parametrar.

Medlemmar i en klass kan inte ha signaturer som bara skiljer sig åt med ref, ref readonly, ineller out. Ett kompilatorfel uppstår om den enda skillnaden mellan två medlemmar av en typ är att en av dem har en ref parameter och den andra har en out, ref readonlyeller in parameter. Metoder kan dock överbelastas när en metod har parametern ref, ref readonly, ineller out och den andra har en parameter som skickas av värdet, som visas i följande exempel. I andra situationer som kräver signaturmatchning, som att dölja eller åsidosätta, , inref, ref readonlyoch out är en del av signaturen och inte matchar varandra.

När en parameter har någon av föregående modifierare kan motsvarande argument ha en kompatibel modifierare:

  • Ett argument för en ref parameter måste innehålla ref modifieraren.
  • Ett argument för en out parameter måste innehålla out modifieraren.
  • Ett argument för en in parameter kan också innehålla in modifieraren. ref Om modifieraren används på argumentet i stället utfärdar kompilatorn en varning.
  • Ett argument för en ref readonly parameter bör innehålla antingen in eller ref modifierare, men inte båda. Om ingen av modifierarna ingår utfärdar kompilatorn en varning.

När du använder dessa modifierare beskriver de hur argumentet används:

  • ref innebär att metoden kan läsa eller skriva värdet för argumentet.
  • out innebär att metoden anger argumentets värde.
  • ref readonly innebär att metoden läser, men inte kan skriva argumentets värde. Argumentet ska skickas med referens.
  • in innebär att metoden läser, men inte kan skriva argumentets värde. Argumentet skickas med referens eller via en tillfällig variabel.

Du kan inte använda de tidigare parametermodifierarna i följande typer av metoder:

  • Asynkrona metoder som du definierar med hjälp av asynkron modifieraren.
  • Iteratormetoder, som innehåller en avkastningsretur eller yield break -instruktion.

Tilläggsmetoder har också begränsningar för användningen av dessa argumentnyckelord:

  • Nyckelordet out kan inte användas på det första argumentet i en tilläggsmetod.
  • Nyckelordet ref kan inte användas på det första argumentet i en tilläggsmetod när argumentet inte är en struct, eller en allmän typ som inte är begränsad till att vara en struct.
  • Nyckelorden ref readonly och in kan inte användas om inte det första argumentet är ett struct.
  • Nyckelorden ref readonly och in kan inte användas på någon allmän typ, även om de är begränsade till att vara en struct.

Egenskaper är inte variabler. Det är metoder. Egenskaper kan inte vara argument för ref parametrar.

ref parametermodifierare

Om du vill använda en ref parameter måste både metoddefinitionen och anropande metoden uttryckligen använda nyckelordet ref , som du ser i följande exempel. (Förutom att anropsmetoden kan utelämna ref när du gör ett COM-anrop.)

void Method(ref int refArgument)
{
    refArgument = refArgument + 44;
}

int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45

Ett argument som skickas till en ref parameter måste initieras innan det skickas.

out parametermodifierare

Om du vill använda en out parameter måste både metoddefinitionen och anropande metoden uttryckligen använda nyckelordet out . Till exempel:

int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod);     // value is now 44

void OutArgExample(out int number)
{
    number = 44;
}

Variabler som skickas som out argument behöver inte initieras innan de skickas i ett metodanrop. Den anropade metoden krävs dock för att tilldela ett värde innan metoden returneras.

Dekonstruera metoder deklarerar sina parametrar med out modifieraren för att returnera flera värden. Andra metoder kan returnera värdetupplar för flera returvärden.

Du kan deklarera en variabel i en separat instruktion innan du skickar den som ett out argument. Du kan också deklarera variabeln out i argumentlistan för metodanropet i stället för i en separat variabeldeklaration. out variabeldeklarationer ger mer kompakt, läsbar kod och förhindrar även att du oavsiktligt tilldelar ett värde till variabeln före metodanropet. I följande exempel definieras variabeln number i anropet till metoden Int32.TryParse .

string numberAsString = "1640";

if (Int32.TryParse(numberAsString, out int number))
    Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
    Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
//       Converted '1640' to 1640

Du kan också deklarera en implicit inskriven lokal variabel.

ref readonly modifierare

Modifieraren ref readonly måste finnas i metoddeklarationen. En modifierare på anropsplatsen är valfri. in Antingen kan modifieraren eller ref användas. Modifieraren ref readonly är inte giltig på anropsplatsen. Vilken modifierare du använder på anropsplatsen kan hjälpa dig att beskriva argumentets egenskaper. Du kan bara använda ref om argumentet är en variabel och kan skrivas. Du kan bara använda in när argumentet är en variabel. Det kan vara skrivbart eller skrivskyddat. Du kan inte lägga till någon av modifierarna om argumentet inte är en variabel, men är ett uttryck. Följande exempel visar dessa villkor. Följande metod använder ref readonly modifieraren för att ange att en stor struct ska skickas med referens av prestandaskäl:

public static void ForceByRef(ref readonly OptionStruct thing)
{
    // elided
}

Du kan anropa metoden med hjälp av ref eller in modifieraren. Om du utelämnar modifieraren utfärdar kompilatorn en varning. När argumentet är ett uttryck, inte en variabel, kan du inte lägga till in eller ref modifierare, så du bör ignorera varningen:

ForceByRef(in options);
ForceByRef(ref options);
ForceByRef(options); // Warning! variable should be passed with `ref` or `in`
ForceByRef(new OptionStruct()); // Warning, but an expression, so no variable to reference

Om variabeln är en readonly variabel måste du använda in modifieraren. Kompilatorn utfärdar ett fel om du använder ref modifieraren i stället.

Modifieraren ref readonly anger att metoden förväntar sig att argumentet ska vara en variabel i stället för ett uttryck som inte är en variabel. Exempel på uttryck som inte är variabler är konstanter, metodreturvärden och egenskaper. Om argumentet inte är en variabel utfärdar kompilatorn en varning.

in parametermodifierare

Modifieraren in krävs i metoddeklarationen men onödig på anropsplatsen.

int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument);     // value is still 44

void InArgExample(in int number)
{
    // Uncomment the following line to see error CS8331
    //number = 19;
}

Med in modifieraren kan kompilatorn skapa en tillfällig variabel för argumentet och skicka en skrivskyddad referens till argumentet. Kompilatorn skapar alltid en tillfällig variabel när argumentet måste konverteras, när det finns en implicit konvertering från argumenttypen eller när argumentet är ett värde som inte är en variabel. Till exempel när argumentet är ett literalvärde eller värdet som returneras från en egenskapsåtkomst. När ditt API kräver att argumentet skickas med referens väljer du ref readonly modifieraren i stället för in modifieraren.

Metoder som definieras med parametrar in kan få prestandaoptimering. Vissa struct typargument kan vara stora och när metoder anropas i snäva loopar eller kritiska kodsökvägar är kostnaden för att kopiera dessa strukturer betydande. Metoder deklarerar in parametrar för att ange att argument kan skickas med referens på ett säkert sätt eftersom den anropade metoden inte ändrar argumentets tillstånd. Om du skickar dessa argument med referens undviker du den (potentiellt) dyra kopian. Du lägger uttryckligen in till modifieraren på anropsplatsen för att säkerställa att argumentet skickas med referens, inte efter värde. Explicit användning in har följande två effekter:

  • Om du in anger på anropsplatsen tvingar kompilatorn att välja en metod som definierats med en matchande in parameter. Annars, när två metoder skiljer sig endast i närvaro av in, är överlagringen efter värde en bättre matchning.
  • Genom att ange deklarerar indu avsikten att skicka ett argument med referens. Argumentet som används med in måste representera en plats som kan refereras direkt till. Samma allmänna regler för out och ref argument gäller: Du kan inte använda konstanter, vanliga egenskaper eller andra uttryck som skapar värden. Annars meddelar utelämnande in på anropsplatsen kompilatorn att det är bra att skapa en tillfällig variabel för att skicka med skrivskyddad referens till metoden. Kompilatorn skapar en tillfällig variabel för att övervinna flera begränsningar med in argument:
    • En tillfällig variabel tillåter kompileringstidskonstanter som in parametrar.
    • En tillfällig variabel tillåter egenskaper eller andra uttryck för in parametrar.
    • En tillfällig variabel tillåter argument där det finns en implicit konvertering från argumenttypen till parametertypen.

I alla föregående instanser skapar kompilatorn en tillfällig variabel som lagrar värdet för konstanten, egenskapen eller andra uttryck.

Följande kod illustrerar dessa regler:

static void Method(in int argument)
{
    // implementation removed
}

Method(5); // OK, temporary variable created.
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // OK, temporary int created with the value 0
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // passed by readonly reference
Method(in i); // passed by readonly reference, explicitly using `in`

Anta nu att en annan metod med hjälp av by-value-argument var tillgänglig. Resultatet ändras enligt följande kod:

static void Method(int argument)
{
    // implementation removed
}

static void Method(in int argument)
{
    // implementation removed
}

Method(5); // Calls overload passed by value
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // Calls overload passed by value.
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // Calls overload passed by value
Method(in i); // passed by readonly reference, explicitly using `in`

Det enda metodanropet där argumentet skickas med referens är det sista.

Kommentar

Föregående kod används int som argumenttyp för enkelhetens skull. Eftersom int inte är större än en referens i de flesta moderna datorer finns det ingen fördel med att skicka en enda int som en skrivskyddad referens.

params modifierare

Inga andra parametrar tillåts efter nyckelordet params i en metoddeklaration och endast ett params nyckelord tillåts i en metoddeklaration.

Den deklarerade typen av params parameter måste vara en samlingstyp. Identifierade samlingstyper är:

Före C# 13 måste parametern vara en endimensionell matris.

När du anropar en metod med en params parameter kan du skicka in:

  • En kommaavgränsad lista med argument av typen av matriselement.
  • En samling argument av den angivna typen.
  • Inga argument. Om du inte skickar några argument är listans params längd noll.

I följande exempel visas olika sätt på vilka argument kan skickas till en params parameter.

public static void ParamsModifierExample(params int[] list)
{
    for (int i = 0; i < list.Length; i++)
    {
        System.Console.Write(list[i] + " ");
    }
    System.Console.WriteLine();
}

public static void ParamsModifierObjectExample(params object[] list)
{
    for (int i = 0; i < list.Length; i++)
    {
        System.Console.Write(list[i] + " ");
    }
    System.Console.WriteLine();
}

public static void TryParamsCalls()
{
    // You can send a comma-separated list of arguments of the
    // specified type.
    ParamsModifierExample(1, 2, 3, 4);
    ParamsModifierObjectExample(1, 'a', "test");

    // A params parameter accepts zero or more arguments.
    // The following calling statement displays only a blank line.
    ParamsModifierObjectExample();

    // An array argument can be passed, as long as the array
    // type matches the parameter type of the method being called.
    int[] myIntArray = { 5, 6, 7, 8, 9 };
    ParamsModifierExample(myIntArray);

    object[] myObjArray = { 2, 'b', "test", "again" };
    ParamsModifierObjectExample(myObjArray);

    // The following call causes a compiler error because the object
    // array cannot be converted into an integer array.
    //ParamsModifierExample(myObjArray);

    // The following call does not cause an error, but the entire
    // integer array becomes the first element of the params array.
    ParamsModifierObjectExample(myIntArray);
}
/*
Output:
    1 2 3 4
    1 a test

    5 6 7 8 9
    2 b test again
    System.Int32[]
*/