1일1알

알아두면 유용한 기타 문법 본문

스터디/C#

알아두면 유용한 기타 문법

영춘권의달인 2021. 11. 9. 19:23

 

 

 

Generic (일반화)

 

List<int> intList;
List<float> floatList;

앞에서 배운 List에도 Generic이 사용되었다. <>안에 타입 형식을 넣어서 원하는 타입의 리스트를 만들 수 있다.

 

class MyList<T>
{
    public T a;
    public T foo(T val)
    {
        return val;
    }
}
MyList<int> myIntList = new MyList<int>();

이런 식으로 클래스에 Generic을 사용하면 원하는 타입으로 객체를 만들 수 있다.

 

object val1 = 3; // val1 : object타입
var val2 = 3; // val2 : int타입

object 타입 : 모든 타입을 포함하는 타입, 참조 타입이다.

var 타입 : 뒤에 오는 변수를 보고 타입을 추측해서 그 타입으로 적용한다.

 

static T Test<T>(T input)
{
    T ret = input;
    return ret;
}

함수에도 Generic을 사용하여 여러 타입을 받을 수 있다.

 

class MyList<T, K>

타입을 여러개 받고 싶다면 <> 안에 여러개를 써주면 된다.

 

class MyList<T> where T : struct // T는 값 형식
class MyList<T> where T : class // T는 참조 형식
class MyList<T> where T : new() // MyList에는 어떠한 인자도 받지 않는 기본 생성자가 있어야 함
class MyList<T> where T : Monster // T는 Monster 혹은 Monster을 상속받은 형식이어야 함

where 문법을 이용하여 T값을 제한할 수도 있다.

 

class MyList<T, K> 
    where T : struct
    where K : struct

타입이 두개라면 이런식으로 하면 된다.


Interface (인터페이스)

 

추상 클래스 : 추상적으로 존재하는 클래스, 추상 클래스는 객체를 생성할 수 없음

abstract class Monster
{
    
}

class 앞에 abstract를 추가하면 됨.

 

abstract class Monster
{
    public abstract void Shout();
}

추상 함수 : 멤버함수 앞에 abstract를 붙임, 추상 클래스의 추상 함수는 상속받은 클래스에서 반드시 구현을 해줘야한다.

추상 함수는 선언만 할 수 있고, 구현은 자식 클래스에서 하면 된다.

 

추상 함수가 있는 클래스는 추상 함수로 만들어야한다.

 

한 클래스는 여러 개의 클래스를 상속받을 수 없다.

이런 상황에서 SkeletonOrc는 Orc의 Shout을 받아야 할지, Skeleton의 Shout을 받아야 할지 알 수 없기 때문이다.

: 다이아몬드 상속 문제

 

하지만 인터페이스는 여러 개를 상속받을 수 있다.

인터페이스 : 함수를 선언만 하고 구현을 하지 않음, 상속받은 클래스에서 함수를 구현

interface IFlyable
{
    void Fly();
}
class Orc : Monster, IFlyable
{   
    public void Fly()
    {

    }    
}

이렇게  IFlyable 인터페이스 부분에서는 함수를 선언만 하고 IFlyable을 상속받은 Orc에서는 Fly함수를 구현한다.

 

abstract 보다는 interface를 사용하는 것이 유연성이 좋다.

 

IFlyable orc = new Orc();

Orc 클래스의 객체를 IFlyable 인터페이스 형식으로 저장할 수 있다. orc에는 IFlyable의 함수가 전부 포함되어 있기 때문? IFlyable를 상속받았기 때문에 IFlyable 의 함수를 orc에서 모두 사용할 수 있다.


Property (프로퍼티)

 

 

클래스의 Get함수와 Set함수를 편하게 쓸 수 있게 해주는 기능?

Get : 멤버변수의 값을 추출할 때 사용

Set : 멤버변수의 값을 수정할 때 사용

 

class Knight //Get, Set 함수를 사용하는 경우
{
    protected int hp;
    public int GetHP() { return hp; }
    public void SetHP(int hp) { this.hp = hp; }
}

class Knight // 프로퍼티를 사용하는 경우
{
    protected int hp;
    public int HP { get { return hp; } set { hp = value; } }
}
Knight knight = new Knight();

//Get, Set 함수를 사용하는 경우
int a1 = knight.GetHP();
knight.SetHP(10);

//프로퍼티를 사용하는 경우
int a2 = knight.HP;
knight.HP = 10;

HP 프로퍼티 하나로 Get, Set 함수를 모두 구현할 수 있다.

 

class Knight
{
    public int hp { get; set; } = 100;
}

자동 구현 프로퍼티 : 멤버변수를 따로 선언하지 않고 프로퍼티를 만들 수 있다.

초기값을 넣어줄 수도 있다. 

 


Delegate (대리자)

 

델리게이트로 채널을 만들고 함수들을 채널에 구독시킨 뒤 채널을 키면 구독한 함수들에게 알림을 보내주는 느낌?

함수 자체를 인자로 넘겨줄 수 있다. (콜백 방식)

delegate int OnClicked();

OnClicked라는 함수 형식을 delegate를 이용하여 생성 ( 반환 : int, 받는 인자 : 없음)

 

static void ButtonPressed(OnClicked clicked)
{
    clicked();
}

이런식으로 OnClicked 라는 함수 형식을 인자로 받을 수 있다.

 

static int TestDelegate()
{
    Console.WriteLine("Test");
    return 0;
}
ButtonPressed(TestDelegate); // Test 출력

TestDelegate 함수는 OnClicked와 같이 반환값이 int고 받는 인자가 없으므로 ButtonPressed에 OnClicked 형식으로 넘겨줄 수 있다.

 

OnClicked clicked = new OnClicked(TestDelegate);
ButtonPressed(clicked);

 

이런 식으로 OnClicked 형식의 객체를 만들어서 만들어진 객체를 OnClicked 타입으로 넘길 수도 있다.

 

static int TestDelegate2()
{
    Console.WriteLine("Test2");
    return 0;
}
OnClicked clicked = new OnClicked(TestDelegate);
clicked += TestDelegate2;
ButtonPressed(clicked); 
// 출력 : Test
//        Test2

이런 식으로 OnClicked 객체에 여러 개의 함수를 넣을 수도 있다.

 

clicked();
// 출력 : Test
//        Test2

만들어진 객체를 직접 호출할 수도 있다.


Event (이벤트)

 

이벤트 : 델리게이트는 만들어진 객체를 어디서든 호출할 수 있지만, 이벤트는 선언된 클래스 내에서만 호출이 가능하다. (델리게이트를 남용하는것을 막기 위한 문법?)

 

public delegate void OnInputKey();
public event OnInputKey Inputkey;

Inputkey를 event로 만들었고, delegate와 형식이 같은 함수를 Inputkey에 추가할 수 있다.

Inputkey에 함수를 추가하는 것은 어디서든 가능하지만, 호출하는 것은 선언된 class 내에서만 가능하다.

(private Get, public Set 느낌)

 

그리고 델리게이트로 만든 형식은 타입으로 사용할 수 있지만, 이벤트는 불가능하다.


Lambda (람다식)

 

람다 : 일회용 함수를 만드는데 사용하는 문법

 

enum ItemType
{
    Weapon,
    Armor,
    Amulet,
    Ring
}
enum Rarity
{
    Normal,
    Uncommon,
    Rare
}
class Item
{
    public ItemType itemtype;
    public Rarity rarity;
}  
List<Item> _items = new List<Item>();
_items.Add(new Item() { itemtype = ItemType.Weapon, rarity = Rarity.Normal });

Item 클래스는 ItemType 변수와 Rarity 변수를 가지고 있다.

_items는 Item의 객체를 저장하고 있는 List이다.

Add에서 쓴 문법은 람다식은 아니고 이렇게 객체의 생성과 동시에 값을 넣어주는 것도 가능한 문법이다.

 

여기서 특정 ItemType과 Rarity를 가지고 있는 Item을 찾으려면 어떻게 해야할까?

가장 먼저 생각할 수 있는 방법은 특정 ItemType과 Rarity를 검사하는 함수를 하나씩 일일이 만들어 주는 것이다.

위의 코드에서는 종류가 얼마 없지만 종류가 많을 경우에는 함수를 굉장히 많이 만들어야 한다.

하지만 델리게이트를 이용해서 아이템을 검사해주는 함수를 받는 함수를 만들면 함수 하나로 해결할 수 있게 되고,

여기서 함수를 넘겨줄 때 일회용 함수인 람다식을 사용할 수 있다.

 

delegate bool ItemSelector(Item item);
static Item FindItem(ItemSelector selector)
{
    foreach(Item item in _items)
    {
        if (selector(item))
            return item;
    }
    return null;
}

이렇게 델리게이트로 ItemSelector 함수 형식을 만들고, 그것을 받는 FindItem 함수를 만들었다.

 

Item item = FindItem(delegate (Item item) { return item.itemtype == ItemType.Weapon; });

 

이것도 람다식은 아니고 무명 함수 라는 것인데, 이렇게 델리게이트를 이용하여 함수를 만들어서 넘겨줄 수 있다.

 

Item item = FindItem((Item item) => { return item.itemtype == ItemType.Weapon; });

이것이 람다식인데, item을 받아서 뒤의 식의 결과를 리턴한다는 뜻이다. 굉장히 직관적인 것 같다.

 

ItemSelector selector = new ItemSelector((Item item) => { return item.itemtype == ItemType.Weapon; });
ItemSelector selector = (Item item) => { return item.itemtype == ItemType.Weapon; };

이렇게 람다식을 이용해서 델리게이트로 만든 타입의 변수? 에 넣어줄 수도 있다.

 

 

람다식에 대한 내용은 이게 끝이고 조금 심화적인? 기능들에 대해 알아보자

delegate Return DelegateEx<T, Return>(T val);

이렇게 델리게이트에도 Generic을 사용할 수 있다. 리턴값은 Return이고, 인자로 받는 값의 타입은 T타입이다.

입력 형식이 하나이고 반환 형식이 있는 모든 함수를 위의 델리게이트를 통하여 넘겨줄 수 있다.

 

그러면 입력이 두 개 이상인 경우는 어떻게 하는지에 대한 의문이 생길 수 있는데,

사실 c#에 모든 경우에 대한 델리게이트들이 구현이 되어있다.

 

Func : 인자 0개 ~ 16개 를 받을 수 있고, out으로 result를 반환한다. (잘 사용 안하는듯)

Action : 인자 0개 ~ 16개 받을 수 있고, 반환값이 없다.

이렇게 델리게이트를 직접 선언하지 않아도 이미 만들어진 애들이 존재한다.

반환이 있으면 Func

반환이 없으면 Action을 사용하면 된다.


Exception (예외 처리)

 

예외 처리 : 예기치 못한 예외적인 상황에 대한 처리

 

try
{

}
catch(Exception e)
{

}
finally
{

}

예외 처리는 위와 같은 try-catch문으로 진행되는데, try에서 실행되던 코드에서 예외가 발생하면 catch문으로 넘어간다.

catch문 옆에 있는 Exception 타입은 모든 예외를 잡아주는 타입이고, 예외의 종류에 따라 타입이 다르다.

 

try
{

}
catch (DivideByZeroException e)
{

}
catch(Exception e)
{

}
finally
{

}

예외의 종류는 굉장히 많은데, 예외의 종류에 따라 예외를 처리하고 싶다면 Exception타입으로 예외를 잡는 catch문보다 위쪽에 catch문을 또 작성하면 된다. finally는 try-catch문과 상관없이 무조건 거치게 되는 부분이다.

 

대표적인 예외적인 상황 : 0으로 나눌 때, 잘못된 메모리를 참조할 때, 오버플로우가 일어났을 때 등

그리고 try문에서 예외가 발생하면 예외가 발생한 뒤의 코드는 실행되지 않는다.


Reflection (리플렉션)

 

 

리플렉션 : X-Ray를 찍는 것? 클래스의 정보를 실행 시간에 뜯어볼 수 있다.

 

class Monster
{
    public int hp;
    protected int attack;
    private float speed;
    void Attack() { }
}
Monster monster = new Monster();
Type type = monster.GetType();

GetType을 통해 Type 타입으로 객체의 타입을 추출할 수 있는데, 이 type에는 굉장히 많은 정보가 있다.

 

 

 FieldInfo[] fields = type.GetFields(System.Reflection.BindingFlags.Public
     | System.Reflection.BindingFlags.NonPublic
     | System.Reflection.BindingFlags.Static
     | System.Reflection.BindingFlags.Instance);

 

GetFields를 통해서 FieldInfo[] 타입으로 정보를 뽑아낼 수 있다고 한다.

 

솔직히 리플렉션은 감이 잘 안온다. 

 

Attribute : 컴퓨터가 확인할 수 있는 주석?


Nullable (널러블)

 

Nullable : 원래 Null이 할당될 수 없는 타입에도 Null을 넣을 수 있게 해주는 문법

 

int? number = null;

타입 뒤에 ? 을 붙여서 null을 넣을 수 있다.

 

if (number.HasValue)
{
    int a = number.Value;
}
else
{
    Console.WriteLine("null");
}

HasValue를 통해 null인지 아닌지를 판별할 수 있고, Value를 통해 값을 추출할 수 있다.

만약 number이 null일 때 Value로 값을 추출하면 프로그램이 죽는다.

 

int b = number ?? -1;

이런 문법도 있는데, number가 null이 아니면 number값이 할당되고, null이면 뒤에 있는 -1이 할당된다.

삼항연산자와 비슷한 느낌이다.

 

Monster monster = null;
int? id = monster?.Id;

monster가 null이면 id에 null을 넣어주고, null이 아니라면 Id값을 찾아서 id에 넣어주는 문법이다.

'스터디 > C#' 카테고리의 다른 글

자료구조 맛보기  (1) 2021.11.08
4일차 : 객체지향 여행  (1) 2021.11.05
2일차 : 코드의 흐름 제어  (1) 2021.11.02
1일차 : 데이터 갖고 놀기  (1) 2021.11.01