Csharp/Objekter

(Omdirigert fra «Csharp/Kapittel3»)

Objekter

rediger

Et objekt i .NET, kan være alt fra et tall til en komplisert 3D modell, det finnes flere typer objekter, vi har class, interface, struct og enum (i tilfeldig rekkefølge) det er mange forskjeller på disse, men vi vil først og fremst konsentrere oss om class i første omgang, og ta en lett gjennomgang av de andre. Det finnes også noe som heter namespace, men dette har ingen effekt på selve programmet, annet en organisering (System er et namespace)

Class, i motsenting til struct, er alltid et referanseobjekt, hva dette vil si, skal vi gå nærmere inn på senere.

public class MyClassName
{
}

Vi begynner fra starten, public er noe som kalles en access modifier, den har en funksjon: hindre at klassen blir brukt utenfor det den var ment for. Public vil si at klassen er tilgjengelig fra alle steder, selv fra andre programmer. vi har endel access modifiers,

  • internal
Objektet er kun tilgjengelig fra det tilhørende prosjektet
Det vil si at hvis du lager en dll som brukes i andre programmer, kan ikke disse programmene se objekter som er deklarert internal.
  • private
Objektet er kun tilgjengelig fra seg selv, eller objektene det er definert innenfor.
Dette er standard. Hvis du ikke spesifiserer noen access modifier, vil C# gå utifra at du mener private.
  • protected
Objektet er kun tilgjengelig fra seg selv, og arvende objekter.
  • protected internal
Objektet er kun tilgjengelig innefor dette prosjektet, seg selv, og arvende objekter (innen samme prosjekt)
  • public
Objektet er tilgjengelig for alle

Vi vil i første omgang kun bruke public, noen variabler kan være protected eller private. Jeg har som vane å skrive protected, fordi det gjør at man slipper å bruke properties innen arvende objekter, men som regel så holder det med private.

Etter public, kommer class, dette er et nøkkelord, som forteller at du vil definere en klasse. MyClassName er rett og slett navnet på klassen, dette kan være det du vil synes er en kort, men beskrivende navn på klassen sin funksjon, et langt navn kan føre til irritasjon etterhvert. Det finnes noen regler for hva et navn kan inneholde:

  • kan ikke starte med et tall, men det kan inneholde et tall ellers i navnet
  • Kan bare inneholde tall eller bokstaver, eller understrek
  • kan ikke inneholde mellomrom (ofte blir understrek benyttet som mellomrom)

Vær obs på at C# gjør forskjell på store og små bokstaver, altså at det er forskjell på A og a, derfor er det viktig å være konsekvent med navngivning. En uskreven regel, er at alle variabelnavn er skrevet med små bokstaver.

Etter klassenavnet, vil vi se to krøllparanteser { } Disse tegnene beskriver en samling med instruksjoner, class, struct, enum, interface og namespace ha disse tegnene. innenfor kan det være definisjoner på nye klasser, enum, struct, interface, funksjoner, egenskaper, event, delegate og variabler. Det kan ikke bli definert nye namespace innefor en klasse.

class kan også ha en rekke nøkkelord

  • abstract
Klassen kan arves, men ikke brukes til å lage objekter med nøkkelordet new, som kalles instansiering.
  • static
Klassen er statisk, og kan ikke arves, eller instansieres. Alle funksjoner må hentes fra klassen uten å bruke en variabel.
Et eksempel på en statisk klasse, er System.Windows.Forms.MessageBox.
  • unsafe
Klassen kan inneholde felt som bruker pekere.
  • sealed
Klassen kan ikke arves.

klasser kan arve fra class og interface, funksjonen dette har, er at man slipper å skrive samme funksjonene om og om igjen, og vil virke logisk når man har lært det. Vi skal gå nærmere inn på interface senere, så i første omgang, snakker vi om arv fra class. arv skrives med et kolon (:) og kun 'en' klasse kan arves fra, men så mange interfaces man trenger.

// Denne klassen skal arves.
public class MyInheritenceClass
{
  public void MyFunction()
  {
  }
}
// Denne klassen arver alt fra MyInheritenceClass
public class MyInheritClass : MyInheritenceClass
{
}

Det vi ser her er at MyInheritenceClass inneholder en funksjon (vi skal gå næremere inn på funksjoner senere) MyInheritClass vil også nå inneholde alle ting som er definert i MyInheritenceClass, pluss sine egne medlemmer (ingen i dette tilfellet)

For å legge til interfaces, gjelder samme syntaks

public class MyDisposableClass : System.IDisposable
{
  public void Dispose()
  {
  }
}

Merk at ved å arve fra interface, 'må' man inkludere alle medlemmer i interfacet.

Struct

rediger

Struct er hva vi kaller en valuetype, dvs. at de som regel vil bli kopiert når de blir referert til, det finnes en rekke unntak som vi vil ta for oss senere. class og struct kan minne om hverandre, men de har to forskjellige nytteområder. Arv i struct gjøres på samme måte som i class, bortsett fra at kun interface objekter er tilgjengelig.

public struct MyStruct
{
}

Structs kan ikke bruke protected eller protected internal access modifierene, grunnen til dette er simpelthen at structs ikke kan arves. Derfor kan felt i structs, bare være public eller private.

public struct Vector
{
  float m_x;
  float m_y;
  public float X { get { return m_x; } set { m_x = value; } }
  public float Y { get { return m_y; } set { m_y = value; } }
  public float Magnitude()
  {
    return (float)Math.Sqrt((double) ((m_x * m_x) + (m_y * m_y) + (m_z * m_z)));
  }
}

Denne strukturen definerer noen vanlige felt og funksjoner i en vektorklasse.

Enum er også en valuetype, og er ment for å gi programmereren en måte å kunne tilegne en bestemt verdi, eller en blanding av dem. Enum kan også arve, men kun fra bestemte typer:

  • byte, sbyte
  • short, ushort
  • int, uint
  • lont, ulong

Hva disse er, går vi nærmere inn på senere ved variabel deklerasjon. La oss lage en enum som onneholder alle dagene i uka

public enum DaysInWeek
{
  Monday,
  Tuesday,
  Wednsday,
  Thursday,
  Friday,
  Saturday,
  Sunday
}

Som vi ser her, lager vi nå en liste skilt med komma (,) men i noen tilfeller, er vi interresert å tilegne dem en verdi, f.eks. fordi vi vil at mandag skal være én, ikke null

public enum DaysInWeek
{
  Monday = 1,
  Tuesday = 2,
  Wednsday = 3,
  Thursday = 4,
  Friday = 5,
  Saturday = 6,
  Sunday = 7
}

Og i andre tilfeller, er vi interresert i å kunne tilegne flere verdier samtidig, da er det viktig at alle tallene går i rekkefølgen 1, 2, 4, 8, 16, 32, 64 etc. Grunnen til dette, er at slike verdier kalles et bitfield, og vi må være obs på athvert bit i et tall har disse verdiene

public enum DaysInWeek
{
  Monday = 1,
  Tuesday = 2,
  Wednsday = 4,
  Thursday = 8,
  Friday = 16,
  Saturday = 32,
  Sunday = 64
}

Nå kan man si at DaysInWeek var = DaysInWeek.Monday & DaysInWeek.Thursday & DaysInWeek.Sunday Hva dette vil si, skal vi gå nærmere inn på i variabel deklerasjon. Man kan også bruke hexadesimaler, som er prefikset med 0x

public enum DaysInWeek
{
  Monday = 0x01,
  Tuesday = 0x02,
  Wednsday = 0x04,
  Thursday = 0x08,
  Friday = 0x10,
  Saturday = 0x20,
  Sunday = 0x40
}

Som ville gitt samme resultat, men kan i enkelte tilfeller være enklere å jobbe med, dette da i enums som brukes som bitfelt, altså at hver verdi representerer ett bit. For å blande sammen verdier i en bitfelt enum, må man bruke en operatør som heter 'eller' (or) som er en logisk operatør. Tegnet for dette er en vertikal strek |, som du finner til venstre for 1, Denne vil 'nesten' fungere som pluss.

1 | 1 = 1
0 | 1 = 1
1 | 0 = 1
0 | 0 = 0

For å hente ut verdien igjen, er man interresert i verdien i kanskje kun ett bit, da bruker man 'og' (and) som også er en logisk operatør. Tegner for dette er vanlig "og" tegn, '&' og vil kun gi tilbake en verdi, dersom begge er satt

1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0

Dermed må man tenke for seg hver verdi som et bit, og da er det kjekt å vite at for hver bit mot høyre, dobler verdien seg, altså vil det se slik ut

Hex:          0x80     0x40     0x20     0x10     0x08     0x04     0x02     0x01
Desimal:       128       64       32       16        8        4        2        1
Binær:    10000000 01000000 00100000 00010000 00001000 00000100 00000010 00000001


Dermed ser man at 00000000 | 00001000 = 00001000 For å hente verdien igjen, Y = X & 00001000, hvis da X = 00001000, da vil den returnere 00001000. Dermed sjekker man ofte om returverdien er den man skal ha tak i.

Merk at '0x' er bare et tegn for å vise at verdien er i heksadesimaler.

Interface

rediger

Interface er i bunn og grunn kun en garanti for at en klasse implementerer en rekke funksjoner eller egenskaper, og definerer ikke noe programkode i seg selv. eksempel på interface er IDisposable. Som regel begynner interface navn på I, grunnen til det, er å skille mellom interfaces og andre datatyper, det er ikke nødvendig å gjøre dette, men anbefales for å holde koden konsekvent. Mange programmerere kommer kanskje aldri til å bruke interfaces, det kommer an på hva du lager om du trenger det eller ikke.

public interface IMyInterface
{
}

Interfaces har noen andre regler for hvordan funksjoner og egenskaper skal se ut, det vil si de skal ikke inneholde noe kode, eller noen access modifier

public interface IEmployee
{
  string FirstName { get; set; }
  string LastName { get; set; }
  int ID { get; } // Denne egenskapen kan kun leses fra
  void Fire();
  event EventHandler Hired;
}

Noen kan spørre hvilken nytte interfaces har, og svaret er rett og slett at det kan i en generell liste bestemme om et objekt støtter en funksjon eller ikke. Et godt eksempel ville vært om vi hadde en liste med objekter, som brukes til utvikling av et GUI, noen av objektene skal tegnes, og andre ikke. Da bruker man et interface, for eksempel. IDrawable på alle objekter som skal tegnes, og deretter sjekke hvert objekt om det implementerer IDrawable med operatøren is.

public interface IDrawable
{
  void Draw(System.Drawing.Graphics g);
}
public class ClassComponent
{
  protected void SomeFunction();
}
public class ButtonComponent : ClassComponent, IDrawable
{
  protected System.Drawing.Point m_pos;
  protected System.Drawing.Size m_size;
  public void Draw(System.Drawing.Graphics g);
  {
    g.DrawRectangle(Pens.Black, new Rectangle(m_pos, m_size));
  }
}
public class Container : System.Drawing.Control
{
  protected List<ClassComponent> m_comps;
  protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
  {
    base.OnPaint(e);
    foreach(ClassComponent comp in m_comps)
    {
      if(comp is IDrawable) // Først sjekker vi om det aktive objektet er av typen IDrawable
        (comp as IDrawable).Draw(e.Graphics);
    }
  }
}

Eksempel

rediger

// Dette er bare et eksempel, og er unødvendig krunglete skrevet, men kun laget for å presisere hvordan klasser og arv fungerer.
using System;
 
// Først definerer vi grunnklassen, som har en funksjon, som skal overskrives i arvende klasser.
public class MyPrintClass
{
  public virtual void PrintString()
  {
  }
  public void PrintClassName()
  {
    Console.WriteLine(this.GetType().FullName);
  }
}
 
// Deretter definerer vi HelloWorld klassen, som skal skrive ut "Hello World!" til skjermen. 
public class MyHelloWorldClass : MyPrintClass
{
   public override void PrintString()
  {
    Console.WriteLine("Hello World!");
  }
}
 
// Deretter lager vi en klasse som skriver ut hva brukeren som er logget på heter. 
public class MyPrintUserClass : MyPrintClass
{
   public override void PrintString()
  {
    Console.WriteLine(Environment.UserName);
  }
}
 
public class Program
{
  public static int Main()
  {
    MyPrintClass print =  new MyHelloWorldClass()  as MyPrintClass;
    print.PrintString(); // Bruk klassens funksjon PrintString, denne er virtual, og kan endres fra klasse til klasse.
    print.PrintClassName(); // Skriv ut navnet på klassen
    print =  new MyPrintUserClass() as MyPrintClass;
    print.PrintString();
    print.PrintClassName(); // Skriv ut navnet på klassen
    return 0; // Returner 0 til operativsystemet, denne verdien kan hentes fra ERRORLEVEL med batch script.
  }
}