czwartek, 09 kwietnia 2015

Zmienne języka C#

Język C# jest językiem z mocnym nadawaniem typów, oznacza to, że wszystkie używane w nim zmienne i obiekty muszą mieć jawnie zadeklarowany typ danych. C# rozróżnia kilka rodzajów typów danych, między innymi typ wartościowy, który został opisany.

using System;

namespace HelloWorld
{
    class Program
    {
        static double pi = 3.14;

        private static void Main(string[] args)
        {
            int l0;
            float liczba1, liczba2 = 4.3f;

            Console.WriteLine("pi = " + pi + " w Main");

            double wynik = dodaj(l0 = 3, liczba2);

            Console.WriteLine("wynik = " + wynik);
            Console.WriteLine("l0 = " + l0 + " w Main");
            Console.WriteLine("pi = " + pi + " w Main");

            Console.ReadLine();
        }

        public static double dodaj(int l0, float b)
        {
            l0 += 8;
            pi--;

            Console.WriteLine("l0 = " + l0 + " w dodaj");

            return l0 + b * pi;
        }
    }
}

Powyższy przykład jest osią tej notatki. Będziemy do niego wracać omawiając poszczególne typy wartości: int (liczba całkowita), float (liczba zmiennoprzecinkowa 32-bitowa) oraz double (liczba zmiennoprzecinkowa 64-bitowa).

Zacznijmy od pierwszej zadeklarowanej zmiennej pi, która została poprzedzona słowem kluczowym static. Wcześniejszy wpis Pierwszy program HelloWorld C# prezentował połączenie static z metodą Main, dzięki czemu dostęp do metody możliwy był bez tworzenia egzemplarza klasy. Połączenie słowa static z typem wartości lub referencji zapewnia identyczną wartość, lub referencję do obiektu, we wszystkich egzemplarzach klasy.

using System;

namespace HelloWorld
{
    class Program
    {
        private static void Main(string[] args)
        {
            MyCounter t0 = new MyCounter();

            Console.WriteLine("t0 i = " + t0.I);

            MyCounter t1 = new MyCounter();
            t1.I = 10;
            Console.WriteLine("SET t1.I = 10");

            Console.WriteLine("t0 i = " + t0.I);
            Console.WriteLine("t1 i = " + t1.I);

            Console.ReadLine();
        }
    }

    class MyCounter
    {
        public static int i = 5;

        public int I
        {
            get { return i; }
            set { i = value; }
        }
    }
}

Powyższy przykład obrazuje wykorzystanie statycznej zmiennej, w którym dla egzemplarza t0 klasy MyCounter wartość zmiennej i domyślnie wynosi 5. Przy tworzeniu obiektu t1 zmieniono wartość zmiennej i na 10. Modyfikacja wartości w obiekcie t1 wpływa również na wartość w obiekcie t0. Warto wspomnieć, że ustawienie wartości odbywa się przy użyciu właściwości obiektu (tutaj znajdziesz więcej informacji).

Wróćmy do naszego głównego przykładu i zmiennej l0 typu int. Należy pamiętać, aby przed użyciem typów wartości, między innymi typu int, wykonać przypisanie wartości przed użyciem zmiennej, inaczej kompilator zgłosi błąd. Wartość zmiennej l0 została przypisana wraz z wywołaniem metody dodaj.

double wynik = dodaj(l0 = 3, liczba2);

Język C# pozwala na deklarowanie kilku zmiennych w jednej instrukcji. Przykładem są zmienne liczba1 oraz liczba2 typu float. W przykładzie wartość zmiennej liczba2 ustawiono na 4.3, natomiast zmienna liczba1 została wyłącznie zadeklarowana. Jeżeli chcemy, aby wartość obu zmiennych wynosiła 4.3, zmienne należy przypisać do siebie (pokazuje to przykład poniżej). C# traktuje wszystkie liczby niecałkowite jako wartości typu double. Jeżeli chcemy przypisać wartość do zmiennej typu float, musimy uzupełnić zapis tej liczby literą F lub f.

float liczba1 = liczba2 = 4.3f;

Podobnie jest w przypadku typu decimal, dla którego przyrostek to M lub m. Poprzedzając informację prezentowaną w tabeli typów wartościowych, stosując przyrostek U lub u, możemy określić użycie typu wyłącznie dodatniego, przez co zakres wartości typu zostanie przesunięty o wartość ujemną. Na koniec informacja o możliwości zapisu w formie szesnastkowej, którą wystarczy poprzedzić sekwencją znaków 0x, np. int a = 0xFFF, czyli 4095.

Warto pamiętać, że kompilator nie pozwala na mieszanie typów. Poniższy przykład nie pozwoli wykonać kompilacji kodu.

float a, double b;

Wielkość liter w nazewnictwie ma znaczenie. Możemy zadeklarować zmienną int alamakota = 123; oraz int alaMaKota = 123;. Dla kompilatora są to dwie różne zmienne. Zastosowanie dwóch niemal identycznych nazw zmiennych może doprowadzić do problemów w późniejszej analizie kodu. Nazwa zmiennej powinna zaczynać się od litery bądź podkreślenia. Kompilator nie dopuszcza, aby nazwa rozpoczynała się od cyfry lub znaku specjalnego (cyfry mogą współtworzyć nazwę zmiennej). Warto unikać używania polskich znaków w nazewnictwie zmiennych. W przypadku, gdy nazwa zmiennej brzmi identycznie jak słowo kluczowe, należy poprzedzić ją znakiem @. Nazwanie zmiennej identycznie jak słowo kluczowe języka nie jest najlepszym pomysłem.

Pamiętaj o tworzeniu "poprawnego kodu", czyli:

  • Klasa nie powinna posiadać zmiennych, których różnica wynika z wielkości liter w nazwie.
  • Nazwy zmiennych składających się z kilku słów np. alaMaKota powinny zostać wyróżnione wielką literą dla każdego słowa, z pominięciem pierwszego.
  • Nazwy zmiennych nie powinny korzystać z słów kluczowych dla języka programowania oraz z polskich znaków.

Słowa kluczowe języka C#

abstract       as             base           bool
break          byte           case           catch
char           checked        class          const
continue       decimal        default        delegate
do             double         else           enum
event          explicit       extern         false
finally        fixed          float          for
foreach        goto           if             implicit
in             int            interface      internal
is             lock           long           namespace
new            null           object         operator
out            override       params         private
protected      public         readonly       ref
return         sbyte          sealed         short
sizeof         stackalloc     static         string
struct         switch         this           throw
true           try            typeof         uint
ulong          unchecked      unsafe         ushort
using          virtual        void           volatile
while
Kontekstowe słowa języka C#
add            alias          ascending      async
await          descending     dynamic        from
get            global         group          into
join           let            orderby        partial
remove         select         set            value
var            where          yield 

Znający C# czytelnicy zapewne zgłoszą uwagi przy użyciu zmiennej pi. Nazwa zmiennej pi wskazuje na stałą wartość matematyczną. W rzeczywistości deklaracja stałej powinna wyglądać tak:

const double Pi = 3.14;
Słowo kluczowe const zapewnia niezmienialność wartości w czasie działania programu. Wyłącznie typy wartości mogą zostać oznaczone jako stałe. Jednocześnie należy pamiętać, że wartość stałej ustalana jest w momencie deklarowania zmiennej, kompilator zgłosi błąd przy próbie zmiany wartości stałej. Warto wspomnieć o konwencji nazewnictwa nakazującego zapisywać nazwy stałych od wielkiej litery.

Analizując przedstawiony kod, należy omówić również zasięg poszczególnych zmiennych. Zmienna pi została zadeklarowana wewnątrz klasy (zmienna globalna), przez co widoczna jest we wszystkich metodach klasy. W przypadku gdy zadeklarujemy zmienną pi wewnątrz metody dodaj, zmienna globalna zostanie praktycznie przysłonięta przez zmienną lokalną metody. Jeżeli chcemy pomimo to dostać się do zmiennej globalnej, musimy podać pełną nazwę zmiennej HelloWorld.Program.Pi. W ramach jednego zasięgu nie mogą istnieć dwie zmienne o takich samych nazwach. Innym aspektem zasięgu zmiennych jest możliwość wpływania na ich wartość. Zmienna l0 w momencie przekazywania wartości metodzie dodaj ustawiana jest na 3. Natomiast wewnątrz metody, wartość zwiększona jest o 8. Zmiana nie wpływa na wartość zmiennej poza metodą. Do metody trafia wartość, a nie referencja, przez co wszystkie operacje wewnątrz metody nie wpływają na zmienną poza nią. Jeżeli chcemy przekazać referencję musimy użyć słowa kluczowego ref lub out. Inaczej jest w przypadku zmiennych globalnych np. pi, modyfikacja wewnątrz metody dodaj wpływa na wartość zmiennej poza nią.

Tabela typów wartości

Typ języka C#		Typ środowiska .NET		Liczba bitów
---------------------------------------------------------------
bool			System.Boolean			1
byte			System.Byte			8
sbyte			System.SByte			8
short			System.Int16			16
ushort			System.UInt16			16
int			System.Int32			32
uint			System.UInt32			32
long 			System.Int64			64
ulong 			System.UInt64			64
float 			System.Single			32
double 		System.Double			64
decimal 		System.Decimal			128
char 			System.Char			16

Deklarując zmienną można naprzemiennie posługiwać się typem języka C# jak i typem środowiska .NET. Metoda GetType zwraca typ środowiska .NET, natomiast sizeof zwraca rozmiar typu danych.

int liczba0 = 5;
System.Int32 liczba1 = 5;

Console.WriteLine(liczba0.GetType());
Console.WriteLine(sizeof(long) + " bajty");

Innym przykładem typów wartości są struktury. Struktura Point z przestrzeni nazw System.Drawing reprezentuje parę współrzędnych "x" i "y", definiując punkt dwuwymiarowy. Zmiana wartości w zmiennej pB nie powoduje zmian wartości zmiennej pA. Oznacza to, że obie zmienne zawierają niezależne kopie swoich wartości. Struktury dokładniej opisane zostały tutaj.

Point pA, pB;
pA = new Point(3, 4);
pB = pA;

Console.WriteLine("pA = " + pA);
Console.WriteLine("pB = " + pB);

pB.X = 10;
pB.Y = 8;

Console.WriteLine("pA = " + pA);
Console.WriteLine("pB = " + pB);

Wszystkie typy wartości języka C# otrzymują domyślną wartość podczas deklaracji. Należy jednak pamiętać, że kompilator nie pozwoli użyć zmiennej przed przypisaniem jej wartości początkowej. Słowo kluczowe default umożliwia nadanie wartości domyślnej dla podanego typu.

object x;
x = default(int);
Console.WriteLine("x = " + x);

x = default(bool);
Console.WriteLine("x = " + x);

C# umożliwia użycie wartości "pustych", wymaga to jednak jawnej deklaracji. Do wykorzystania są dwa równoważne warianty: bool? b = null; oraz Nullable<bool> b = null;. Pierwszy zapis jest skróconym zapisem drugiego. Wykorzystując wartości puste, warto korzystać z właściwości HasValue, która sprawdza, czy zmienna przechowuje wartość. Oczywiście można użyć operatora porównania if (a == null) { ... }. Należy pamiętać, aby zabezpieczyć się przed wykorzystaniem takiej zmiennej. Próba odczytania wartości poprzez właściwość Value dla zmiennej bez ustawionej wartości spowoduje powstanie wyjątku. Kompilator zgłosi również błąd przy próbie przypisania wartości zmiennej zerowej do innej zmiennej tego samego typu, która nie jest zmienną zerową. Problem można rozwiązać, wykorzystując operator łączący ??. Poniższy kod demonstruje działanie. Jeżeli zmienna a będzie miała wartość null, zostanie przypisana do niej wartość false.

bool? a = null;
if (a.HasValue)
{
    Console.WriteLine("a = " + a);
}
else
{
    Console.WriteLine("zmienna a jest pusta");

    a = a ?? false;
    Console.WriteLine("a = " + a.Value);
}

Notatka zawiera jeszcze jeden nieomówiony typ string, czyli łańcuch znaków. Za każdym razem, gdy korzystamy z metody WriteLine

Post jest wprowadzeniem do pierwszych działań z językiem C#. W gruncie rzeczy nie należy się przejmować, jeżeli nie wszystko jest zrozumiałe. To tylko teoria, praktyka jeszcze przed nami w kolejnych notatkach.

Troska Robert