Kihagyás

C# alapok I

Az óra célja egy alap nyelvi tudás biztosítása. Feltételezzük, hogy a hallgató teljesítette a Programozás { Alapjai, I, II } kurzusokat, illetve az Alkalmazásfejlesztés I-et, így ismeri a C, C++ és Java nyelveket. Ez lehetővé teszi, hogy az órán ne az alap programozási konstrukciókkal, hanem a C# nyelv tulajdonságaival foglalkozzunk.

Az anyag a Reiter István - C# programozás lépésről lépésre könyv példáit veszi alapul, melyek során az eszköztár bemutatásra kerül.

Típusok

Referencia- és értéktípusok

Mint ahogy a Java nyelvből már ismerős lehet, a C#-ban is minden típus egy ősosztályból származik. Jelen esetben ez az ősosztály a System.Object. A közös ősosztályon felül viszont megkülönböztetünk érték és referenciatípusokat. A referenciatípusú objektumok a heapen foglalnak helyet, míg az értéktípusú változók a stacken (kivéve, ha egy referenciatípusú objektum adattagjai). Ez az információ segíthet megérteni a függvények paraméterátadását.

  • értéktípus: char, enum, bool, struct, int, float, double, ... (minden beépített numerikus típus)
  • referenciatípus: string, saját és beépített osztályok, delegate.

Amennyiben egy referenciatípusú változót értékül adunk egy másiknak, akkor nem az értéke másolódik le, hanem a két változó ugyanarra a memóriaterületre (objektumra) mutat. Ily módon az egyik változó módosítása mindkét változón keresztül érezteti hatását.

1
2
3
4
5
double variable = 3.14;
double another = variable;
another = 2.72;

Console.WriteLine(variable); // 3.14

A double another = variable; sor lefutásrakor a variable értéke átmásolódik az another változó memóriaterületére. Így annak változtatása nincs kihatással a variable értékére.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class RefType {
    public double valueType;
}
// ...
RefType rt = new RefType();
rt.valueType = 3.14;

RefType another = rt;
another.valueType = 2.71;
Console.WriteLine(rt.valueType); // 2.71

A RefType another = rt; sor lefutásakor az rt és az another objektumok ugyanarra a memóriaterületre mutatnak, így az another változtatása kihatással van az rt értékére is.

Konstansok

A konstansokat két különálló kulcsszóval tudjuk létrehozni: const és readonly. A const kulcsszó segítségével egy objektum nem módosíthatóvá tehető és ebben az esetben deklaráció és a definíció egyszerre kell megtörténjen. A readonly módosítóval ellátott objektumok is módosíthatatlanok, viszont itt a deklaráció és a definíció szétválik, a definíciónak elég a konstruktorban megtörténnie.

1
2
3
4
const int a = 3; // Ok.
a = 4; // Fordítási hiba.

const int b; // Fordítási hiba.

Konvenció szerint (nem csak C# esetén) a konstans változókat NAGY BETŰVEL szokás írni, ezzel is jelezve a fejlesztőknek, hogy egy konstanst használ.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class ReadOnlyExample {
    public readonly double readonlyValue;

    public ReadOnlyExample(double value) {
        readonlyValue = value;
    }

    public void MagicMethod(double value) {
        readonlyValue = value; // Fordítási hiba.
    }
}

Egy readonly változó kaphat úgy értéket, mint a const, de nem kötelező a deklaráció során értéket adni neki. A konstruktorban is változtatható, viszont a MagicMethod fordítási hibát eredményez.

Nullable típusok

Egy referenciatípusú változó deklarációja során a keretrendszer automatikusan null értékkel inicializálja azt (azaz nem mutat memóriaterületre). Az értéktípusú változók ezzel szemben alapértelmezett értékekre inicializálódnak (pl. int: 0, bool: false), és kézzel sem tudjuk a null értéket beállítani. Ahhoz, hogy az értéktípusú változókhoz is hozzá lehessen rendelni a "még nem definiált" jelzőt, a nullable típusmódosítót kell alkalmazni. Ez két módon történhet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int? nullableInt = null;
Nullable<int> anotherNullable = null;

if (nullableInt.HasValue)
    Console.WriteLine(nullableInt.Value);

if (anotherNullable == null)
    anotherNullable = 3;

Console.WriteLine(anotherNullable.Value); // 3

A két írási mód csak a szintaxisban különbözik, a háttérben ugyanarra a konstrukcióra fordulnak. Két propertyt érdemes megemlíteni:

  • Value: visszaadja a változóban tárolt értéket.
  • HasValue: visszaadja, hogy a változó tárol-e értéket (bool).

Amennyiben megpróbálnánk kiíri az anotherNullable értékét még azelőtt, hogy a hármas számot értékül adjuk neki, InvalidOperationException-t kapunk vissza Nullable object must have a value. üzenettel.

Implicit típusok

A nyelv szigorúan típusos, az implicit típusok csak kényelmi szolgáltatásként érthetők el. A var kulcsszó használatakor a változó típusát a fordító a jobb oldali kifejezésből fogja "kitalálni". Konvenció szerint beépített típusoknál nem szokás használni.

1
2
var rt = new RefType();
rt.valueType = 3.14;

Az rt változó RefType típusú és a kódrészlet teljesen ekvivalens a RefType rt = new RefType(); sorral.

Névtelen típusok

Amennyiben egy típus használatához nincs szükségünk a konkrét megnevezésére, akkor használhatóak a névtelen típusok.

1
2
var anonymVariable = new { id=0, city="Szeged" };
Console.WriteLine(anonymVariable.city);

Osztályok

Osztályok a class kulcsszó segítségével deklarálhatók. Az öröklődés a C++-hoz hasonlóan class Child : Base konstrukcióval valósítható meg. A C++-től eltérően a C# nem támogatja a többszörös öröklődést, így egy osztálynak csak egy őse lehet, viszont több interfészt is implementálhat.

Adattagok láthatósága

Módosító Jelentése az adattagra nézve
public Az elérés nincs korlátozva.
protected Csak a tartalmazó osztályból és leszármazottaiból elérhető.
private Csak a tartalmazó osztályból elérhető.
internal Csak a tartalmazó assembly-ből elérhető.
protected internal Csak a tartalmazó assembly-ből vagy a leszármazott osztályokból elérhető.
private protected Csak a tartalmazó osztályból vagy a tartalmazó assembly-n belül leszármazott osztályokból elérhető.

Az osztályokban létrehozott adattagok alapértelmezett láthatósága private.

Tulajdonságok

A "tulajdonság" (property) egy speciális adattagja az osztálynak, mely lehetővé teszi privát változók kontrollált hozzáférését. Első ránézésre adattagok, viszont speciális metódusok legtöbbször publikus láthatósággal ellátva.

A get kulcssóz használható a változó értékének elkérésére, a set pedig a beállítására. Mindkét esetben lehetőség van különböző validációk és számítások elvégzésére mielőtt visszaadnánk (vagy beállítanánk) az értéket.

Szerencsére a property-ket nem kell kézzel írni, a fejlesztőkörnyezet biztosít gyorsbillentyűket a létrehozásukhoz.

  • prop + TAB + TAB
1
public int MyProperty { get; set; }
  • propfull + TAB + TAB
1
2
3
4
5
6
private int myVar;

public int MyProperty {
    get { return myVar; }
    set { myVar = value; }
}

Amennyiben az alapértelmezetten felül szükséges további számítás vagy validálás a get és a set blokkokban, akkor az a következő módon tehető meg:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private int myVar;

public int MyProperty {
    get { return myVar * 3; }
    set {
        if (value < 1 || value > 500) {
            myVar = 0;
        } else {
            myVar = value;
        }
    }
}

Parciális osztályok

A nyelv megengedi az osztályok több részletben definiálását, ezeket parciális (partial) osztályoknak nevezzük. Minden részletnél ki kell írni a partial class-t ugyanazzal a láthatósági módosítóval, különben fordítási hibát eredményez.

Ezek az osztályok főként a WinForms technológiánál kerülnek elő, amikor az osztály egyik részét a fejlesztőkörnyezet generálja (GUI összekattintgatásból generált kód), a másik felét pedig a programozó írja (eseménykezelés).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// PartialClass.cs
public partial class PartialClass {
    public int FirstPart { get; set; }
}

// Program.cs
public partial class PartialClass {
    public void PrintToConsole() {
        Console.WriteLine(FirstPart);
    }
}

class Program {
    static void Main(string[] args) {
        var pClass = new PartialClass();
        pClass.FirstPart = 3;
        pClass.PrintToConsole();
    }
}

Interfészek

Az interfész magában meghatároz egy függvényhalmazt, melyet az interfészt implementáló osztálynak kell megvalósítania. A nyelv nem támogatja a többszörös öröklődést, így egy osztálynak csak egy őse lehet, viszont több interfészt is megvalósíthat. Míg az osztályok esetében az alapértelmezett láthatósági módosító private, az interfészek esetében ez public.

Tartalmazhat:

  • metódusokat
  • propertyket
  • indexelőket
  • eseményeket (következő óra)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
interface ISample { void SampleMethod(); }

class Implmentation : ISample {
    public void SampleMethod() { /* ... */ }

}
class Program {
    static void Main() {
        var obj = new Implmentation();
        obj.SampleMethod();
    }
}

Amennyiben egy osztály több interfészt implementál, akkor vesszővel elválasztva kell felsorolni őket. Ha több interfész deklarálná a SampleMethod-ot, akkor az implementációnak expliciten ki kell mondania, hogy melyiket valósítja meg.

1
2
3
4
class Implmentation : ISample, ISample2 {
    public void ISample.SampleMethod() { /* ... */ }
    public void ISample2.SampleMethod() { /* ... */ }
}

Ha az interfészeken kívül az osztály öröklődik egy másik osztályból, akkor az öröklődésnek kell az első helyre kerülnie, majd az interfészlista következik. A példából látható, hogy az elnevezési konvenció szerint egy I betű kerül minden interfész neve elé: IInterfaceName.

Amennyiben az osztály a csak bizonyos interfészek bizonyos metódusait implementálja, úgy nem lehet példányt létrehozni belőle, abstract-tá kell tenni azt. Ebben az esetben az abstract kulcsszóval kell ellátni, illetve a nem implementált metódusokat fel kell tüntetni.

1
2
3
4
5
abstract class Implmentation : ISample, ISample2, ISample3 {
    public void ISample.SampleMethod() { /* ... */ }
    public void ISample2.SampleMethod() { /* ... */ }
    public abstract void ISample3.SampleMethod(); // "abstract implementáció"
}

Kiterjesztett metódusok

Már létező típusokhoz új metódusokat tudunk hozzáadni anélkül, hogy azokat módosítanánk vagy származtatnánk belőlük. Ezeket extension method-oknak nevezzük (és maradjunk az angol névnél).

Egy ilyen metódus létrehozásához egy statikus osztály szükséges statikus metódussal.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static class StringReverser {
    public static string Reverse(this string input) {
        char[] array = input.ToCharArray();
        Array.Reverse(array);
        return new String(array);
    }
}
// ...
string alkfejl = "Alkalmazasfejlesztes II";
Console.WriteLine(alkfejl);
Console.WriteLine(alkfejl.Reverse());

Ami megfigyelhető: public static class ... osztályban public static TÍPUS ... (this TÍPUS név) és egy új objektumot ad vissza a new String(...) által. Ezután a TÍPUS típusú változóra hívható paraméter nélkül a függvény.

Metódusok

Referencia szerinti paraméterátadás

Paraméterek átadása történhet érték és referencia szerint is. Érték szerinti átadás esetében a paraméter lemásolódik és a metódus ezt a másolatot kapja meg (ProgAlap-on volt ilyesmiről szó). A referencia szerinti paraméterátadásnál egy objektumra mutató referencia (szintén ProgAlap - pointer) kerül átadásra, nem történik másolás. Ily módon a függvényen belül történt változások láthatók a függvényhívás után is.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static void DoMagic(PartialClass p) {
    p.FirstPart = 5;
}
static void Main(string[] args) {
    var pClass = new PartialClass();
    pClass.FirstPart = 3;
    pClass.PrintToConsole(); // 3

    DoMagic(pClass);
    pClass.PrintToConsole(); // 5
}

Fontos kiemelni, hogy a referenciával kapott objektum (a tényleges memóriaterület) állapotát módosíthatjuk, magát a referenciát nem. Amennyiben a függvényen belül egy új objektumot hozunk létre, azt hozzárendeljük a paraméterhez, ez nem fog látszódni a függvény lefutása után.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
static void DoMagic(PartialClass p) {
    p = new PartialClass();
    p.FirstPart = 5;
}
static void Main(string[] args) {
    var pClass = new PartialClass();
    pClass.FirstPart = 3;
    pClass.PrintToConsole(); // 3

    DoMagic(pClass);
    pClass.PrintToConsole(); // 3
}

Ha szeretnénk módosítani a referenciatípusú paraméter referenciáját, azt külön jelezni kell a paraméterlistában a ref módosítóval. A parciális osztályban bemutatott példa továbbgondolása a következő.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static void DoMagic(ref PartialClass p) {
    p = new PartialClass();
}
static void Main(string[] args) {
    var pClass = new PartialClass();
    pClass.FirstPart = 3;
    pClass.PrintToConsole();

    DoMagic(ref pClass);
    pClass.PrintToConsole();
}

Out paraméter

Referencia szerinti paraméterátadásnál már inicializált paramétert kell átadnunk. Lehetőség van inicializálatlan paramétert is átadni az out kulcsszó segítségével, ami azt jelzi, hogy a függvény a futása során inicializáljz azt (fordítási hiba, ha elmarad).

Erre példa a public static bool TryParse (string s, out int result);. A függvény igaz értékkel tér vissza, ha sikerült a szöveget számmá konvertálnia, és a result változóban adja vissza az eredmény számot.

1
2
3
if (int.TryParse(Console.ReadLine(), out int result)) {
    Console.WriteLine(result);
}

Alapértelmezett és opcionális paraméterek

Függvények paraméterlistájában lehetőség van alapértelmezett értékek megadására. Ezek a paraméterlista végén szerepelnek, és függvényhíváskor nem kötelező ezeket a paramétereket átadni.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void ExampleMethod(int required,
                          string optionalstr = "def",
                          int optionalint = 10) {
    Console.WriteLine($"{required} {optionalstr} {optionalint}");
}

ExampleMethod(5); // 5 def 10
ExampleMethod(5, "custom"); // 5 custom 10
ExampleMethod(5, "custom", 1); // 5 custom 1
ExampleMethod(3, optionalint: 4); // 3 def 4
ExampleMethod(3, , 4); // Fordítási hiba.

Amennyiben ki szeretnénk hagyni a "középső" paramétert, akkor meg kell nevezni a megfelelő opcionális paramétert, aminek értéket adunk (különben a példa utolsó sorában 4-et az optionalstr-nek szeretné értékül adni).

Feladatok

Egy számológép alkalmazás írása.

  • Új .NET Core konzolalkalmazás
  • * 5 4 alakban várja az alkalmazás a sorokat a konzolról (string.Split())
  • * = { +, -, *, / } (összeadás, kivonás, szorzás, osztás)
  • Ha nem jó a formátum, írja ki, hogy "Rossz formátum, helyes pl. + 5 4"
  • Egy jó sor után írja ki az eredményt az alkalmazás és várjon újabb inputot amíg az "exit" sor nem érkezik.

A gyakorlati hét végén (szeptember 19) publikálásra kerül egy referenciaalkalmazás.

Hivatkozások

Reiter István - C# programozás lépésről lépésre

Access Modifiers (C# Reference)

Value types (C# reference)

Reference types (C# Reference)

Constants (C# Programming Guide)

Nullable value types (C# reference)

Implicitly typed local variables (C# Programming Guide)

Partial Classes and Methods (C# Programming Guide)

interface (C# Reference)

Properties (C# Programming Guide)

Extension Methods (C# Programming Guide)

Passing Parameters (C# Programming Guide)


Utolsó frissítés: 2023-08-07 09:17:05