11.15 C#

Zasady dotyczące konwersji są w C# podobne do tych z C++, choć są bardziej wzorowane na Javie. W szczególności, konwersje prowadzące do utraty informacji muszą być jawne.

Warto za to pokrótce wspomnieć o różnicach w definiowaniu i wywoływaniu funkcji. W C# nie ma bezpośredniej implementacji typu wskaźnikowego i referencyjnego jaki znamy z C/C++. Istnieją za to mechanizmy które pozwalają definiować funkcje semantycznie równoważne funkcjom C++ z parametrami wskaźnikowymi i referencyjnymi, a więc pozwalające funkcji na dostęp do oryginału a nie kopii argumentu wywołania.

Deklarację parametru w nagłówku funkcji można poprzedzić modyfikatorem ref lub out. Znaczenie jest następujące:

ref
Odpowiedni argument musi być zainicjowany przed wywołaniem funkcji, ale funkcja może zmienić jego wartość i ta zmiana będzie widoczna w programie wołającym. Funkcja zatem pracuje nie na kopii, ale na oryginale: odpowiada to zatem przekazywaniu argumentu przez odniesienie (referencję).
out
Odpowiedni argument nie musi być zainicjowany przed wywołaniem funkcji; funkcja nadaje mu wartość która będzie widoczna w programie wołającym. Ten przypadek również umożliwia więc pracę funkcji na oryginale. Różnica między tym i poprzednim przypadkiem polega tylko na tym, że argumenty odpowiadające parametrom typu out nie muszą być przed wywołaniem inicjowane, za to funkcja musi nadać im wartość. W poprzednim przypadku argumenty musiały być zainicjowane, natomiast funkcja mogła ale nie musiała zmieniać ich wartości.
Jeśli parametr funkcji jest zadeklarowany jako typu out lub ref, to ten modyfikator musi pojawić się również przed odpowiednim argumentem wywołania tej funkcji. W ten sposób patrząc na wywołanie wiemy, czy funkcja otrzyma kopię czy oryginał. Uniknięto w ten sposób kłopotu jaki sprawiają argumenty referencyjne w C++, gdzie patrząc na wywołanie nie wiemy, czy argument zostanie przekazany przez wartość czy przez odniesienie, a więc czy funkcja będzie pracować na kopii czy na oryginale.

Inaczej również zaimplementowany jest mechanizm przekazywania do funkcji zmiennej liczby argumentów. Ostatni parametr funkcji można zadeklarować z modyfikatorem params. Parametr ten powinien wtedy być typu tablicowego. Jeśli tak zadeklarowaliśmy funkcję, to w miejsce tego parametru podczas wywołania możemy dostarczyć dowolną liczbę argumentów typu odpowiadającego typowi tablicy: wszystkie one będą dostępne w funkcji jako elementy tablicy. W szczególnym przypadku zadeklarowanym typem elementów tablicy może być typ object. Wtedy odpowiadające tej tablicy argumenty mogą być dowolnego typu, w tym typu wbudowanego, jak na przykład int. W tym ostatnim przypadku ich wartości zostaną „opakowane” w zmiennych obiektowych (odpowiadających obiektom klas opakowujących z Javy, jak klasa Integer czy Double). Wewnątrz funkcji można je wtedy „odpakować” za pomocą rzutowania.

Rozpatrzmy następujący program:


P85: Fun.cs     Przesyłanie argumentów w C#

      1.  using System;
      2.  
      3.  class  Raport {
      4.  
      5.      static int LIMIT = 1050;
      6.  
      7.      public static int licz(ref String str, out int sum,
      8.                                    params object[] dane) {
      9.          Console.WriteLine("...Dane od " + str);
     10.          int ile =0;
     11.          sum = 0;
     12.          for (int i = 0; i < dane.Length; i++) {
     13.              if (dane[i] is String) ile++;
     14.              else                   sum += (int) dane[i];
     15.          }
     16.          str = sum >= LIMIT ? "POWYZEJ" : "PONIZEJ";
     17.          return ile;
     18.      }
     19.  }
     20.  
     21.  class Fun {
     22.      public static void Main() {
     23.          int    sum, ile;
     24.          String str;
     25.  
     26.          str = "Kasjer_1";
     27.          ile = Raport.licz(ref str, out sum,
     28.                           "Nowak", 100, 50, 50, 80, 20,
     29.                           "Byrski", 50, 50,
     30.                           "Kociniak", 100, 50, 100, 50,
     31.                           "Malinowski", 70, 30);
     32.          pr(str,ile,sum);
     33.  
     34.          str = "Kasjer_2";
     35.          ile = Raport.licz(ref str, out sum,
     36.                           "Tarski", 200, "Zdziarski", 700,
     37.                                             700, 100, 100);
     38.          pr(str,ile,sum);
     39.      }
     40.  
     41.      private static void pr(String str, int ile, int sum) {
     42.          Console.WriteLine(str + " limitu. Klientow: " +
     43.                                 ile + ". Utarg: " + sum);
     44.      }
     45.  }

W linii siódmej definiujemy w klasie Raport funkcję licz której pierwszy parametr jest rodzaju ref, drugi out, a trzeci params, a więc „zbiera” wszystkie argumenty wywołania poza pierwszymi dwoma.

Jako pierwszy argument funkcja pobiera napis str który drukuje (linia 9), a następnie zmienia (linia 16) na napis 'POWYZEJ' lub 'PONIZEJ' w zależności od wartości zmiennej sum. Zmiana ta będzie widoczna w funkcji wywołującej.

Zmienna sum jest rodzaju out więc jej wartość wejściowa nie jest używana; zmienna ta w ogóle nie musiała być zainicjowana. Funkcja oblicza sumę wpłat od klientów i umieszcza ją w tej zmiennej dzięki czemu wartość jej będzie widoczna w funkcji wywołującej.

Pozostałe argumenty są zbierane w tablicy dane. W pętli (linie 12-15) za pomocą operatora is sprawdzany jest typ kolejnego argumentu. Jeśli był to String to znaczy, że argumentem tym było nazwisko kolejnego klienta i zwiększamy wartość zmiennej ile która jest licznikiem klientów. Jeśli nie był to napis, to znaczy, że jest to opakowana liczba całkowita; rozpakowujemy ją za pomocą rzutowania (operator (int) wyłuskuje z obiektu zapakowaną tam liczbę). O otrzymaną wartość zwiększamy zmienną sum.

W ten sposób funkcja zwraca trzy wartości: poprzez return liczbę klientów, poprzez zmienną str (rodzaju ref) napis mówiący czy suma wpłat była poniżej czy powyżej limitu zdefiniowanego w linii piątej, i wreszcie poprzez zmienną sum rodzaju out sumę wszystkich wpłat.

Funkcja wywołująca, czyli Main z klasy Fun, posyła te dane do funkcji pr która drukuje rezultat obliczeń. Ta funkcja nie modyfikuje otrzymywanych danych, więc przekazywane są one tym razem przez wartość:

    ...Dane od Kasjer_1
    PONIZEJ limitu. Klientow: 4. Utarg: 800
    ...Dane od Kasjer_2
    POWYZEJ limitu. Klientow: 2. Utarg: 1800
Zauważmy, że wywołując funkcję licz w liniach 27 i 35 przed odpowiednimi argumentami musieliśmy umieścić modyfikatory refout. Pozostałe argumenty (siedemnaście w pierwszym wywołaniu, siedem w drugim) są przemieszanymi wartościami typu Stringint — wszystkie one znajdą się w funkcji jako elementy tablicy dane.

T.R. Werner, 21 lutego 2016; 20:17