본문 바로가기
C#

2. 고급 문법

by 노오오오오옹 2021. 6. 21.

https://www.csharpstudy.com/CSharp/CSharp-Intro.aspx

 

C# 프로그래밍 언어 - C# 프로그래밍 배우기 (Learn C# Programming)

C# 프로그래밍 언어 C#은 마이크로소프트에서 개발된 객체 지향 프로그래밍 언어로서 Java 나 C++와 비슷한 면들을 많이 가지고 있다. C#을 이야기 하면 자연스럽게 .NET Framework을 함께 이야기 해야

www.csharpstudy.com

 

1. C# Indexer

  • Indexer

클래스 객체의 데이터를 배열 형태(인덱스로 엑세스 가능)로 만든 것이다.

즉 클래스 객체는 배열이 아니지만, 배열처럼 [] 연산자로 접근이 가능하다.

Sample sample = new Sample();
sample[0] = "First";

 

  • 정의

인덱서는 this[]를 써서 클래스의 프로퍼티처럼 get,set을 정의한다.

class Sample
{
    private const int MAX = 100;
    private string name;

    // 정수 배열 데이터
    private int[] data = new int[MAX];

    // 인덱서 정의, int 타입 사용
    public int this[int index]
    {
    	get
        {
            if (index < 0 || index >= MAX)
            {
                throw new IndexOutOfRangeException();
            }

            else
            {
                return data[index];
            }
        }

        set
        {
            if(index >= 0 && index < MAX)
            {
                data[index] = value;
            }
        }
    }
};

class Program
    {
        static void Main(string[] args)
        {
            Sample sample = new Sample();
            
            // setter
            sample[1] = 1024;
            
            // getter
            int i = sample[1];
        }
    }

 

2. 접근 제한자

  • 접근 제한자

접근 제한자는 외부로부터 접글을 제한할 때 사용한다

접근 제한자 설명
public 모든 외부에서 이 타입을 엑세스할 수 있다. 
internal 동일한 Assembly 내에 있는 다른 타입들이 엑세스 할 수 있고, 다른 어셈블리에서는 접근이 불가하다.
protected 파생클래스에서 이 클래스 멤버를 엑세스할 수 있다.
private 동일 클래스/구조체 내의 멤버만 접근 가능하다.
  • 클래스 : 5가지 접근제한자(public, internal, private, protected, internal protected)가 가능하다. 멤버는 private, namespace안 클래스는 internal이 디폴트이다.
  • 구조체 : 상속 X이기 떄문에 3가지 접근 제한자(public : internal, private)
  • 인터페이스, 열거형 : 기본적으로 public이며 별도의 접근 제한자를 사용하지 않는다.

 

  • 접근 제한자 사용

클래스 필드는 기본적으로 private을 사용한다. 

internal class Sample // 동일 어셈블리 내의 다른 타입만 이 클래스로 접근가능함.
{
    private int mId = 0;
    
    public string Name { get; set; }
    
    public void Run(int id) {}
    
    protected void Execute() {} // MyClass와 MyClass의 자식에서 접근 가능
}

 

3. 클래스 상속

  • 파생 클래스(자식)

부모 클래스로 부터 상속받아 만들어진 클래스이다.

// 베이스 클래스
public class Animal
{
   public string Name { get; set; }
   public int Age { get; set; }
}

// 파생클래스
public class Dog : Animal
{       
   public void HowOld() 
   {
      // 베이스 클래스의 Age 속성 사용
      Console.WriteLine("나이: {0}", this.Age);
   }
}

public class Bird : Animal
{       
   public void Fly()
   {
      Console.WriteLine("{0}가 날다", this.Name);
   }
}

 

  • 추상 클래스

abstract 키워드를 붙여진 클래스르 의미한다. 추상클래스는 객체를 직접 생성(new 연산자)할 수 없다.

멤버에 붙여진 abstract 키워드는 반드시 구현해야하는 의미이다.

public abstract class PureBase // class 앞 abstract는 추상 클래스
{
    public abstract int GetFirst(); // 멤버함수의 abstract는 반드시 구현하라는 의미
    public abstract int GetNext();
}

public class Derive : PureBase
{
    private int num = 1;

    public override int GetFirst()
    {
        return num;
    }

    public override int GetNext()
    {
        return ++num;
    }
}

 

  • as 연산자와 is 연산자
  • as : 객체를 지정된 클래스타입으로 변환하는데 사용한다. (class or null)
  • is : is 앞의 객체가 특정 클래스 타입이나, 인터페이스를 갖고있는지 판단한다. (true or false)
class First { }
class Second : First { }

class Program
{
    static void Main(string[] args)
    {
        Second second = new Second();

        new Program().Test(second);
    }

    public void Test(object obj)
    {
        // as 연산자
        First fst = obj as First; // First or null 리턴

        // is 연산자
        bool ok = obj is First; // true or false 리턴
        
        // 캐스팅
        First fst2 = (First)obj; // First or Exception 리턴하니, catch 필요!
    }
}

 

4. 정적 static

  • static 메소드

인스턴스 메서드와 달리 객체를 생성하지 않고 호출한다.

public class MyClass
{
    ...
    
    public static int Run() // 정적 메소드
    {
        return 1;
    }
}

int n = MyClass.Run();

 

  • static 속성과 필드

메소드와 마찬가지로 [클래스 명.속성 명]과 같이 사용한다.

public static int id; 

※ static 필드는 프로그램이 종료되기 전까지 4동일한 메모리를 계속해서 사용함

 

  • static 클래스

클래스 멤버 모두 static 멤버로 구성되어있다. 이 또한 마찬가지로 [클래스명.ㅇㅇㅇ] 방식으로 이용한다.

public static class MyUtility
{
   private static int ver;

   // static 생성자
   static MyUtility()
   { 
      ver = 1;
   }

   public static string Convert(int i)
   {
      return i.ToString();
   }

   public static int ConvertBack(string s)
   {
      return int.Parse(s);
   }
}

static void Main(string[] args)
{
   string str = MyUtility.Convert(123);
   int i = MyUtility.ConvertBack(str);
}

 

5. 제네릭

  • 제네릭

C++의 템플릿과 비슷한 개념으로 클래스, 인터페이스, 메소드에 <T>와 같은 타입 파라미터를 붙여 구현한다.

런타임에 제네릭 타입으로부터 지정된 타입의 객체를 생성한다. 

class MyStack<T> // <T> 만 있으면 된다.
{
    T[] mElements
    int pos = 0;

    public MyStack()
    {
        mElements = new T[100];
    }

    public void Push(T t)
    {
        mElements[pos++] = t;
    }

    public T Pop()
    {
        return mElements[pos--];
    }
}

// 타입만 명시하면 된다!
Stack<int> st1 = new Stack<int>();
Stack<double> st2 = new Stack<double>();

 

  • NET 제네릭 클래스들

System.Collections.Generic 네임 스페이스는 모두 제네릭 타입이다.

List<T>, Dictionary<T>, LinkedList<T> 클래스가 있다.

List<string> lt = new List<string>();
lt.Add("홍길동");
lt.Add("이태백");

Dictionary<string, int> dic = new Dictionary<string, int>();
dic["길동"] = 100;
dic["태백"] = 90;

 

  • 제네릭 타입 제약

타입 파라미터로 Value, Reference, 상속, 인터페이스 등을 지정할 수 잇따.

이때 Where T : 제약조건으로 제약을 가한다.

class MyClass<T> where T : struct {} // Value
class MyClass<T> where T : class {} // Reference
class MyClass<T> where T : new() {} // 디폴트 생성자
class MyClass<T> where T : MyBase {} // MyBase의 자식
class MyClass<T> where T : IComparable {} // 인터페이스

// 좀 더 복잡한 제약들
class EmployeeList<T> where T : Employee, IEmployee, IComparable<T>, new()
{
    ...
}

// 복수 타입 파라미터 제약
class MyClass<T, U> where T : class where U : struct
{
    ...
}

 

6. 인터페이스

인터페이스는 클래스와 비슷하지만 직접 구현하지않고, 단지 정의만 한것이다.

즉 추상멤버로만 구상된 추상 클래스와 개념이 유사하다. 그래서 추후 사용할 때 해당 클래스에서 구현해야한다.

public class MyConnection : Component, IDbConnection, IDiposable
{
    // IDbConnection을 구현 필수!
    // IDisposable을 구현 필수!
}

 

  • 인터페이스의 정의와 구현
public interface IComparable // interface 키워드 사용
{
    int CompareTo(object obj); // 접근 제한자 X
}

public class Sample : IComparable
{
    private int key;
    private int val;

    public int CompareTo(object obj) // 인터페이스의 함수 구현
    {
        Sample sample = (Sample)obj;

        return this.key.CompareTo(sample.key);
    }
}

 

  • 인터페이스 사용

인터페이스의 장점은 어떤 클래스인지 상관없이, 공통적으로 구현된 멤버 함수를 사용할 수 있다.

public void Run()
{
   // 특정 DB Connection(인터페이스를 사용하니깐!)을 신경 쓸 필요가 없다
   IDbConnection dbCon = GetDbConnection();
   dbCon.Open();
   
   if (dbCon.State == ConnectionState.Open)
   {
      dbCon.Close();
   }
}

public IDbConnection GetDbConnection() // IDbConnection 인터페이스를 리턴
{
   IDbConnection dbConn = null;
   
   string cn = ConfigurationManager.AppSettings["Connection"];
   switch (ConfigurationManager.AppSettings["DbType"])
   {
      case "SQLServer":
         dbConn = new SqlConnection(cn);
         break;
         
      case "Oracle":
         dbConn = new OracleConnection(cn);
         break;
         
      case "OleDB":
         dbConn = new OleDbConnection(cn);
         break;         
   }
   
   return dbConn;
}

 

7. delegate 

  • 기초 이해

어떤 메소드를 다른 메소드에 전달할 수 있도록 만들어졌다.

class MyClass {}
void RunB(MyClass c) {} 

// 위의 함수를 사용할려면?
MyClass c = new MyClass();
RunB(c);

// 파라미터로 정수, 객체등의 데이터를 메소드에 보내고있다.
// 다만 물리적 데이터가 아닌 메소드만 보낼 수 있을까? => 델리게이트

정수, 객체, 메소드 전달의 과정

 

델리게이트를 정의하는 방법은 delgeate 키워드를 사용한다.

delegate int MyDelegeate(string s);

// 델리게이트 객체를 메소드 호출 파라미터에 넣어준다.
void Run(MyDelegate method) {}

MyDelegate d = new MyDelegate(메소드1); // 델리게이트도 일종의 클래스이므로 같은 방법으로 생성한다.
Run(d);

※ 입력 파라미터와 리턴타입이 델리게이트에서 중요하다!

※ 델리게이트는 .NET Delegate.MulticastDelegate 클래스를 사용하고, 이를 따로 파생할 순 없다.

 

델리게이트로 부터 실제 메소드를 호출하는 방법은?

i = m.Invoke("123"); // Invoke(), BeginInvoke() 메소드를 사용한다.
i = m("123"); // Invoke를 생략할 수 있다.

 

  • 델리게이트 샘플
class Program
{
    delegate int MyDelegate(string s);

    static void Main(string[] args)
    {
        new Program().Test();
    }

    void Test()
    {
        MyDelegate d = new MyDelegate(StringToInit); // 델리게이트 객체 생성

        Run(d); // 메소드 전달
    }

    int StringToInit(string s) // 델리게이트 대상이되는 메소드
    {
        return int.Parse(s);
    }

    void Run(MyDelegate d) // 델리게이트를 전달받는 메소드
    {
        int i = d("123");

        Console.Write(i);

    }
}

 

  • 델리게이트 개념

C++의 함수 포인터와 비슷한 개념으로 메소드 파라미터와 리턴 타입에 대한 정의를 한다.

이후 동일한 파라미터와 리턴 타입을 가진 메소드를 서로 호환해서 사용한다.

class MyClass
{
    private delegate void RunDelegate(int i);

    private void RunThis(int val) // 1024
    {
        Console.WriteLine("{0}", val);
    }

    private void RunThat(int val) // 0x400
    {
        Console.WriteLine("0x{0:X}", value);
    }

    public void Perform()
    {
        RunDelegate run = new RunDelegate(RunThis);
        run(1024); // run = RunThis와 같다.

        //run = new RunDelegate(RunThat); 
        run = RunThat;

        run(1024);
    }
}

class Program
{
    static void Main(string[] args)
    {
        MyClass mc = new MyClass();

        mc.Perform();
    }
}

※ 둘다 파라미터로 int 타입, 리턴은 void라 사용할 수 있다.

 

  • 델리게이트로 메소드 파라미터로 전달하기
class MySort
{
    public delegate int CompareDelegate(int i1, int i2); // int Delegate(int, int) 

    public static void Sort(int[] arr, CompareDelegate comp)
    {
        if (arr.Length < 2)
            return;

        Console.WriteLine("함수 Prototype: " + comp.Method);  // 델리게이트.Method: 프로토 타입

        int ret;
        for (int i = 0; i < arr.Length - 1; i++)
        {
            for (int j = i + 1; j < arr.Length; j++)
            {
                ret = comp(arr[i], arr[j]);

                if (ret == -1)
                {
                    int tmp = arr[j];
                    arr[j] = arr[i];
                    arr[i] = tmp;
                }
            }
        }

        Display(arr);
    }

    static void Display(int[] arr)
    {
        foreach (var i in arr) // auto와 같음
            Console.Write(i + " ");

        Console.WriteLine();
    }
}

class Program
{
    static void Main(string[] args)
    {
        (new Program()).Run(); // static 메소드가 아니지만 델리게이트기 때문에 가능함
    }

    void Run()
    {
        int[] a = { 5, 53, 3, 7, 1 };

        // 올림차순으로 소트
        MySort.CompareDelegate compDelegate = AscendingCompare;
        MySort.Sort(a, compDelegate);

        // 내림차순으로 소트
        compDelegate = DescendingCompare;
        MySort.Sort(a, compDelegate);
    }

    int AscendingCompare(int i1, int i2) // int Delegate(int, int)
    {
        if (i1 == i2)
            return 0;

        return (i2 - i1) > 0 ? 1 : -1;
    }

    int DescendingCompare(int i1, int i2) // int Delegate(int, int)
    {
        if (i1 == i2)
            return 0;

        return (i1 - i2) > 0 ? 1 : -1;
    }
}

 

  • 델리게이트 필드와 속성

델리게이트는 클래스의 필드나 속성으로 사용가능하다. 

class MyArea : Form
{
    public MyArea()
    {
        this.MouseClick += delegate { MyAreaClicked(); };
    }

    public delegate void ClickDelegate(object sender);
    public ClickDelegate MyClick; // 델리게이트 필드
    //public ClickDelegate Click { get; set; } // 속성으로도 가능함.

    void MyAreaClicked() // MyArea 클릭시 아래 함수가 자동으로 호출된다.
    {
        if (MyClick != null)
        {
            MyClick(this);
        }
    }
}

class Program
{
    static MyArea area;

    static void Main(string[] args)
    {
        area = new MyArea();
        area.MyClick = Area_Click;
        area.ShowDialog();
    }

    static void Area_Click(object sender)
    {
        area.Text = "MyArea 클릭!";
    }
}

 

  • 멀티캐스트 델리게이트

여러개의 메소드를 할당할 수 있는 델리게이트이며, += 연산자를 통해 추가한다.

class Program
{
   static MyArea area;

   static void Main(string[] args)
   {
      area = new MyArea();

      // 복수 개의 메서드를 delegate에 할당
      area.MyClick += Area_Click;
      area.MyClick += AfterClick;

      area.ShowDialog();
   }

   static void Area_Click(object sender)
   {
      area.Text += " MyArea 클릭! ";
   }

   static void AfterClick(object sender)
   {
      area.Text += " AfterClick 클릭! ";
   }
}

 

  • 이벤트

델리게이틔 단점을 해소하기 위해 특수한 형태로 제작했다.

  • =(할당) 연산자로 기존 리스트가 사라짐
  • 클래스 외부에서도 호출할 수 있다.
area = new MyArea();

area.MyClick += Area_Click;
area.MyClick += AfterClick;

area.MyClick = Area_Click; // 기존 리스트 삭제, 덮어쓰기 중
area.MyClick(null); // 델리게이트를 Area 클래스가 아닌 외부 클래스에서 호출한다

 

  • 이벤트의 특성
  • = 연산자 사용 불가, +=(이벤트 핸들러 추가)와 -=(이벤트 핸들러 삭제) 가능
  • 외부 클래스에서 이벤트를 호출할 수 없다.
class MyArea : Form
{
    public MyArea()
    {
        this.MouseClick += delegate { MyAreaClicked(); };
    }

    public delegate void ClickEvent(object sender);
    public event ClickEvent MyClick; // 이벤트 필드

    ...
}

class Program
{
    static MyArea area;

    static void Main(string[] args)
    {
        area = new MyArea();

        // 이벤트 핸들러 추가
        area.MyClick += Area_Click;
        area.MyClick += AfterClick;

        // 이벤트 핸들러 삭제
        area.MyClick -= Area_Click;

        // Error: 이벤트 직접호출 불가
        //area.MyClick(this);

        area.ShowDialog();
    }

    ...
}

 

  • 델리게이트 vs 함수포인터
델리게이트 함수포인터
객체의 주소(객체 레퍼런스)와 메소드 주소 함수에 대한 주소 값
프로토타입이 같으면 모든 클래스의 메소드 할당 가능 특정 1개의 클래스 지정
멀티 캐스트(1개 이상의 메소드 레퍼런스 보유) 하나의 함수 포인터 보유
Type Safety 보장 Type Safety 보장 X
// C 함수 포인터 예제
void myfunc(int x) 
{
    printf( "%d\n", x );
}

void main()
{
    // 함수포인터 정의 및 초기화
    void (*f)(int);    
    f = &myfunc;

    f(2);
}

// C++ Pointer to member 예제
class Cls
{
public:
    void myfunc(string str)
    {
        cout << str << endl;    
    }
};
 
void main()
{
    // Pointer to member function 정의
    void (Cls::*fp)(string);
    fp = &Cls::myfunc;

    // Cls 객체 생성 및 객체 포인터 지정
    Cls obj;
    Cls* pObj = &obj;
  
    // Cls 객체에서 함수포인터 사용
    (pObj->*fp)("hello");
}

 

8. 무명 메소드

  • 무명 메소드

메소드명이 없는(미리 정의 X) 메소드이다.

// 무명 메소드 delegate(파라미터) { 실행문장; };
delegate(int p1) { Console.Write(p1); };

 

  • 무명 메소드 사용
public partial class Form1 : Form
{
   public Form1()
   {
      InitializeComponent();

      this.button1.Click += new System.EventHandler(this.button1_Click);  // 이미 정의됨.
      this.button2.Click += delegate(object s, EventArgs e) // 무명 메소드 지정
      {
         MessageBox.Show("버튼2 클릭");
      };
   }

   private void button1_Click(object sender, EventArgs e)
   {
      MessageBox.Show("버튼1 클릭");
   }
}

 

  • 델리게이트 vs 무명 메소드

delegate 키워드는 델리게이트와 무명 메소드 둘다 사용된다.

// Delegate
public delegate int SumDelegate(int a, int b); // delegate 타입을 가리킨다.
SumDelegate sumDel = new SumDelegate(mySum); // delegate 객체를 생성하고 할당한다.
                      
// 무명 메소드 (이벤트 핸들러 이용)
button1.Click += new EventHandler(delegate(object s, EventArgs a) { MessageBox.Show("OK"); });
button1.Click += (EventHandler) delegate(object s, EventArgs a) { MessageBox.Show("OK"); };

// 무명 메소드 (이미 어떤 델리게이트로 사용되는지 안다면 핸들러 생략 가능)
button1.Click += delegate(object s, EventArgs a) { MessageBox.Show("OK"); };
button1.Click += delegate { MessageBox.Show("OK"); };

 

델리게이트 타입을 사용하는 곳에, 무명 메소드를 사용하면 컴파일 에러가 발생한다.

Invoke()는 델리게이트의 파라미터가 몇 개인지, 리턴 값이 무엇인지를 모른다. 즉 무명 메소드가 불가능하다.

// 컴파일 에러 발생
this.Invoke(delegate {button1.Text = s;} );

// 파라미터와 리턴 값을 명시해야함.
MethodInvoker mi = new MethodInvoker(delegate() { button1.Text = s; });
this.Invoke(mi);

// 축약된 표현
this.Invoke((MethodInvoker) delegate { button1.Text = s; });

/* 
MethodInvoker는 입력 파라미터가 없고, 리턴 타입이 void인 델리게이트이다.
MethodInvoker는 System.Windows.Forms 에 다음과 같이 정의되어 있다.

public delegate void MethodInvoker();
*/

 

9. 람다식

람다식 문법 : (입력 파라미터) => { 실행할 블럭 }; 으로 표현한다.

// 기본적인 람다식 표현
Action<string> print = str => Console.WriteLine(str); 
print("Hello"); // Hello 출력

// 입력 파라미터가 없다
() => Write("NO");

// 입력 파라미터가 있다. (n1, n2)로 해도 된다!
Action<int, int> sum = (int n1, int n2) => Console.WriteLine(n1 + n2);
sum(1, 2);

 

람다식을 통해 delegate와 무명 메소드를 더 간략히 사용할 수 있다.

// 일반적인 델리게이트
this.button1.Click += button1_Click; // new System.EventHandler(button1_Click); 표현이 같다.

private void button1_Click(object sender, EventArgs e)
{
   ((Button)sender).BackColor = Color.Red;
}

// 무명 메소드
this.button1.Click += delegate(object sender, EventArgs e)
{
   ((Button)sender).BackColor = Color.Red;
};

// 람다식
this.button1.Click += (sender, e) => ((Button)sender).BackColor = Color.Red; // 실행 문장이 1줄이면 생략 가능

 

10. 익명 타입

C# 3.0 부터 클래스를 정의하지 않아도 되는 익명 타입이 생겼다.

  • 익명 타입은 읽기 전용이므로 프로퍼티를 갱신할 수 없다.
  • 키워드 var를 사용
// 익명타입 : new { 프로퍼티1 = 값1, 프로퍼티2 = 값2 };
var t = new { Name = "홍길동", Age = 20 };
string s = t.Name;

 

배열로도 생성하고 사용할 수 있따.

private void RunTest()
{
    var v = new[] 
    {
        new { Name="Lee", Age=33, Phone="02-111-1111" },
        new { Name="Kim", Age=25, Phone="02-222-2222" },
        new { Name="Park", Age=37, Phone="02-333-3333" },
    };

    // LINQ Select를 이용해 Name과 Age만 갖는 새 익명타입 객체들을 리턴.
    var list = v.Where(p => p.Age >= 30).Select(p => new { p.Name, p.Age });
    foreach (var a in list)
    {
        Debug.WriteLine(a.Name + a.Age);
    }
}

 

 

11. 확장 메소드 1-2

12. partial

13. dynamic

14. await

'C#' 카테고리의 다른 글

1. 기초 문법  (0) 2021.06.20

댓글