parameteroverføring
Børre Stenseth
C#>Parametere

Parameteroverføring

ref | out | param
Hva
Parameteroverføring

Parameteroverføring i C# skiller seg litt fra Java. De grunnleggende prinsippene er de samme: enkle, primitive, typer kopieres og objekter generelt overføres ved referanse. Enkle datatyper er sammenlignbare med de vi finner i Java: bool, char, sbyte, short, int, long, byte, ushort, uint, ulong, float, double, decimal. I tillegg har C# enumeration og structs som ikke er primitive eller enkle typer, men som på samme måte som enkle typer handteres ved verdi, ikke referanse.

Det er tre ting ved parametere vi skal se litt nærmere på: eksplisitte ref-parametere(ref), out-parametere(out) og variable antall parametere(param).

ref

Normalen er altså at vi overfører enkle typer ved verdi og andre typer (klasser, arrays) ved referanse. Vi kan endre dette ved å tvinge enkle typer til å overføres ved referanse ved å merke parameterene med nøkkelordet ref. Merk at vi må merke parameteren med ref både i den formelle og den aktuelle parameterlista. Vi ser på noen eksempler.

Først tar vi for oss en situasjon der parameteren er en collection, en ArrayList:

class params2
{
    // arrayList with and without ref
    public params2(){Console.WriteLine("case 2");}
     protected void reverse_unrefed(ArrayList L)
    {
        for (int ix = 0; ix < L.Count / 2; ix++)
        {
            String temp = (String)L[ix];
            L[ix] = L[L.Count - ix - 1];
            L[L.Count - ix - 1] = temp;
        }
    }
    protected void reverse_refed(ref ArrayList L)
    {
        for (int ix = 0; ix < L.Count / 2; ix++)
        {
            String temp = (String)L[ix];
            L[ix] = L[L.Count - ix - 1];
            L[L.Count - ix - 1] = temp;
        }
    }
    public void show()
    {
        ArrayList List = new ArrayList(5);
        List.AddRange(new String[] 
                     { "en", "to", "tre", "fire", "fem" });
        Console.WriteLine(String.Format(
                        "Original: {0},{1},{2},{3},{4}",
                        List[0].ToString(),List[1].ToString(),
                        List[2].ToString(),List[3].ToString(),
                        List[4].ToString()
                        ));
        reverse_unrefed(List);
        Console.WriteLine(String.Format(
                        "Reversed without ref: {0},{1},{2},{3},{4}",
                        List[0].ToString(), List[1].ToString(), 
                        List[2].ToString(),List[3].ToString(), 
                        List[4].ToString()
                        ));
        reverse_refed(ref List);
        Console.WriteLine(String.Format(
                        "Reversed  with ref: {0},{1},{2},{3},{4}",
                        List[0].ToString(), List[1].ToString(), 
                        List[2].ToString(),List[3].ToString(), 
                        List[4].ToString()
                        ));
    }
}
case2

Effekten av å angi parameteren som ref er ingen. Vi får reversert rekkefølgen i begge tilfelle. Siden det vi overfører er et objekt, ikke en enkel type, er overføringen alltid ved referanse.

Vi prøver med heltall:

class params1
{
    // integer with and without ref
    public params1(){Console.WriteLine("case 1");}
    protected void dobbel_refed(ref int i)
    {
        i = 2 * i;
    }
    protected void dobbel_unrefed(int i)
    {
        i = 2 * i;
    }
    public void show()
    {
        int r = 20;
        int newr = r;
        dobbel_unrefed(newr);
        Console.WriteLine(
                String.Format("Dobbel av {0} uten ref er {1}", 
                             r, newr));
        newr = r;
        dobbel_refed(ref newr);
        Console.WriteLine(
                String.Format("Dobbel av {0} med ref er {1}", 
                              r, newr));
    }
}
case1

Her ser vi at vi får en varig endring av parameteren når vi bruker ref.

Vi prøver med string:

class params4
    {
        // string with and without ref
        public params4(){Console.WriteLine("case 4");}
        protected void reverse_unrefed(String L)
        {
            Char[] cL = L.ToCharArray();
            for (int ix = 0; ix < L.Length / 2; ix++)
            {
                char temp = cL[ix];
                cL[ix] = cL[cL.Length - ix - 1];
                cL[cL.Length - ix - 1] = temp;
            }
            L = new String(cL);
        }
        protected void reverse_refed(ref String L)
        {
            Char[] cL = L.ToCharArray();
            for (int ix = 0; ix < L.Length / 2; ix++)
            {
                char temp = cL[ix];
                cL[ix] = cL[cL.Length - ix - 1];
                cL[cL.Length - ix - 1] = temp;
            }
            L = new String(cL);
        }
        public void show()
        {
            String List = "12345";
            Console.WriteLine("Original: " + List);
            reverse_unrefed(List);
            Console.WriteLine("Reverse without ref: " + List);
            reverse_refed(ref List);
            Console.WriteLine("Reverese with ref: " + List);
        }
    }
case4

Her er det også forskjell om vi bruker ref eller ikke ref. Det vil si at string oppfører seg som en enkel type i denne sammenhengen. String er litt spesiell. En string er det som i fagterminologien kalles "immutable". Det vil si at en string aldri kan endres. Når vi programmerer det som tilsynelatende er endringer til en string, skapes det alltid en ny string. F.eks. vil den tredje linja nedenfor opprette en helt ny string, med en ny referanse.

	String S;
	S="hallo";
	S=S+" alle sammen";

Likevel vil altså parameteroverføring av string som eksplisitt ref-parameter virke etter hensikten.

String array overføres alltid som referanse, som andre arrays:

class params3
{
    // string array with and without ref
    public params3(){Console.WriteLine("case 3");}
    protected void reverse_unrefed(String[] L)
    {
        for (int ix = 0; ix < L.Length / 2; ix++)
        {
            String temp = L[ix];
            L[ix] = L[L.Length - ix - 1];
            L[L.Length - ix - 1] = temp;
        }
    }
    protected void reverse_refed(ref String[] L)
    {
        for (int ix = 0; ix < L.Length / 2; ix++)
        {
            String temp = L[ix];
            L[ix] = L[L.Length - ix - 1];
            L[L.Length - ix - 1] = temp;
        }
    }
    public void show()
    {
        String[] List= new String[] { 
                           "en", "to", "tre", "fire", "fem" };
        Console.WriteLine(String.Format(
                         "Original: {0},{1},{2},{3},{4}",
                        List[0], List[1], List[2], 
                        List[3], List[4]));
        reverse_unrefed(List);
        Console.WriteLine(String.Format(
                        "Reversed without ref: {0},{1},{2},{3},{4}",
                        List[0], List[1], List[2], 
                        List[3], List[4]));
        reverse_refed(ref List);
        Console.WriteLine(String.Format(
                       "Reversed with ref: {0},{1},{2},{3},{4}",
                        List[0], List[1], List[2], 
                        List[3], List[4]));
    }
}
case3

out

Ved å merkere en parameter som out bestemmer vi at vi skal eksportere en verdi fra et funksjonskall via parameterlista. Det som skiller out-parametre fra det vi kan oppnå ved ref, er at parameteren ikke trenger å være initialisert. Vi trenger altså ikke gi noen fornuftig verdi inn i denne parameteren. Igjen må vi merke parameteren både i den formelle og den aktuelle parameterlista. Dette eksempelet som regner ut både areal og omkrets i en sirkel kan illustrere dette:

class params5
{
    // out parameter, string and integer
    public params5(){Console.WriteLine("case 5");}
    protected void produceString(out String L)
    {
        L = "Circle with radius: ";
    }
    protected void produceAreaAndPerimeter(float r,
                                          out float area,
                                          out float circ)
    {
        area= (float)Math.PI * r * r;
        circ = (float)Math.PI * 2 * r;
    }
    public void show()
    {
        String S; 
        float r,a,c;
        r = 1.0f;
        produceString(out S);
        produceAreaAndPerimeter(r, out a, out c);
        Console.WriteLine(
            String.Format(
                         "{0}{1} have area: {2} and perimeter: {3}",
                          S,r,a,c));
    }
}
case5

param

I C# kan vi ha et variabelt antall parametere i en funksjon. En parameter kan settes opp til å tolkes som en liste. Du ser bruk av denne mekanismen i flere av eksemplene på denne siden. F.eks. Convert.Format() fungerer slik. Eksempelet nedenfor illustrerer litt kreativ parameteroverføring:

class params6
  {
      // Variable number of params
      public params6(){Console.WriteLine("case 6");}
      class A
      {
          String myString;
          public A(String s) { myString = s; }
          public override String ToString(){return myString;}
      }
      class B
      {
          int myInt;
          public B(int t) { myInt = t; }
          public override String ToString() 
          { 
              return Convert.ToString(myInt); 
          }
      }
      protected String produceString(String s,params int[]numbers)
      {
          String newS = s;
          for (int ix = 0; ix < numbers.Length; ix++)
              newS += Convert.ToString(numbers[ix]);            
          return newS;
      }
      protected String produceString(String s, params object[]obj)
      {
          String newS = s;
          for (int ix = 0; ix < obj.Length; ix++)
              newS += obj[ix].ToString();
          return newS;
      }
      public void show()
      {

         String t1=produceString("here we go: ",1,2,3,4);
         Console.WriteLine("producedString:"+t1);
         String t2 = produceString("here we go: ", 
                                 new A(" Hurra for "),
                                 new B(17),
                                 new A(". mai"));
         Console.WriteLine("producedString:" + t2);      
      }
  }
case6
Referanser
Du kan eksperimentere med dette og noen andre temaer i programmet:
https://svn.hiof.no/svn/psource/Csharpspikes/language/summary
Vedlikehold

B.Stenseth, november 2005

(Velkommen) C#>Parametere (Properties)