cyphen156

C# 버전별 핵심 기능 (1.0 ~ 7.0) 본문

프로그래밍/C#

C# 버전별 핵심 기능 (1.0 ~ 7.0)

cyphen156 2023. 12. 1. 13:33

 

C#의 역사에 대한 모든 사항은 MS에서 제공하고 있습니다.

C#의 역사 - C# 가이드 - C# | Microsoft Learn

 

C#의 역사 - C# 가이드 - C#

이 언어의 초창기 버전은 어떤 모습이었으며 이후 어떻게 변했는가?

learn.microsoft.com

이 글에서는 일부 기능과 핵심 변경사항에 대해서만 서술합니다.

C# 1.0 

Delegate: C#에서 delegate는 메서드에 대한 참조를 저장할 수 있는 타입입니다. 다른 언어의 함수 포인터와 유사하지만, 타입 안전성과 객체 지향적 특성을 갖는다. 

사용법 (//뒤의 내용은 사용자 지정인수이다.)

delegate //return_type //function_Name (//props);

예제 코드를 보면 publisher에는 TestMethod가 존재하지 않지만 Subscriber 인스턴스 생성시에 참조등록되었다. 

using System;

// 대리자 이벤트 핸들러
delegate void myEventHandler();

class Publisher
{
    // 공유가능한 메서드 이벤트 등록
    public static event myEventHandler myEvent;
    public void RunEvent()
    {
        myEvent();
    }
}

class Subscriber
{
    //구독자 클래스의 생성자 호출시 Publisher 클래스의 공유 메서드에 TestMethod 등록
    public Subscriber()
    {
        Publisher.myEvent += TestMethod;
    }

    public void TestMethod()
    {
        Console.WriteLine("이벤트 메서드 호출!!");
    }
}

class MainClass
{
    public static void Main(string[] args)
    {
        Publisher pub = new Publisher();
        Subscriber sub = new Subscriber();
        pub.RunEvent();
    }
}

C# 2.0

<Generic>

일반화 프로그래밍에 대해서는 기본 개념에서 일부 다뤘었다. 

여러 타입에 대한 대응 코드를 일일이 만드는 것 보다 동적타이핑을 허용하고, 메서드 오버로딩을 통해 서로 다른 타입에도 하나의 코드를 통해 클래스, 메서드를 구현하고, 이벤트 처리하는것을 가능하도록 도와준다.

사용법

//access_limites //return_type //class/method_name <Type> (T[] 매개 변수)
using System;

// 제네릭 클래스
class GenericClass <T>
{
    private T[] values;
    private int index;

    public GenericClass(int length)
    {
        values = new T[length];
        index = length;
    }

    public void setValue(T item, int index)
    {
        this.values[index] = item;
    }

    public void printClassValue()
    {
        foreach(T item in values)
        {
            Console.WriteLine(item);
        }
    }
}

class MainClass
{
    // 제네릭 메서드
    static void PrintValue<T>(T[] values)
    {
        foreach (var i in values)
        {
            Console.WriteLine(i);
        }
    }

    public static void Main(string[] args)
    {
        // 제네릭 메서드 구현
        int[] intValues = { 1, 2, 3 };
        double[] doubleValues = { 1.1, 2.2, 3.3 };

        PrintValue<int>(intValues);
        PrintValue<double>(doubleValues);

        // 제네릭 클래스 구현

        GenericClass<int> intArr = new GenericClass<int>(3);
        GenericClass<double> doubleArr = new GenericClass<double>(3);

        intArr.setValue(1, 0);
        intArr.setValue(2, 1);
        intArr.setValue(3, 2);

        doubleArr.setValue(3.33, 2);
        doubleArr.setValue(2.22, 1);
        doubleArr.setValue(1.11, 0);

        intArr.printClassValue();
        doubleArr.printClassValue();
        return;
    }
}

부분클래스(partial)

클래스가 너무 길어지면 코드를 서로 다른 파일 분할하여 작성할 수 있다. 컴파일 과정중에 하나의 클래스로 결합된다.

Nullable 

자료형에 null로 값을 넣을 수 있도록 허용한다. 잘못쓰면 인스턴스가 null, undefinded가 되어 예기치 못한 에러를 발생시킬 수 있으니 사용시 주의가 필요하다.

C# 3.0

getter와 setter 자동구현

매번 private 속성에 대한 getter와 setter메서드를 구현할 필요 없이 단 한줄의 코드로 자동으로 구현하도록 허용한다.

복잡한 자료형일때는 직접 구현할 수도 있다.

사용법 : 변수 선언시 { get; set; }선언

// name속성에 대한 자동 구현
private string name { get; set; }

// name속성에 대한 직접 구현
private string name;

public void setName(string name) {
	this.name = name;
}

public string getName() {
	return this.name;
}

Lambda(람다식)

익명메서드(화살표 함수)

Func 대리자와 Acation 대리자를 통해 구현가능하다.

Func 대리자는 리턴해야할 자료형이 존재할때 사용하고, Action 대리자는 리턴 형이 void일 때 사용한다.

사용법

// 기본 람다식
(props) => {
	//Action here
}

// Func 대리자
Func <int int> square = x => x * x;

// Action 대리자
Action < string > greet = name => {
	string greeting = "Hello " + name;
    Console.WriteLine(greeting);
};
// 람다를 통한 덧셈 계산
using System;

delegate int Add(int a, int b);

class MainClass {
	public static void Main(string[] args) {
    	Add add = (a, b) => a + b;
        Console.WriteLine(add(1, 2));
    }
}
더보기

여담으로 사실 나는 람다식이 왜 필요한지 아직도 모르겟다. 단 한번만 사용할 함수면 그냥 본래 로직 코드에서 절차적 방법으로 로직을 작성하는것이 훨씬 간편하고, 변수 스코프의 지역성을 이용해 원본 변수를 보호하고 싶다면 임시변수 Temp를 사용하면 된다. 인자로 함수를 넘겨 콜백하고 싶다고? 그럼 참조로 보내면 되지?, 아 어쨋든 코드가 간결해지고 깔끔해진다고? 이건 잘 모르겟네 궂이 () => {}로 한번 더 감싸지 않는다고 코드가 더러워지나? 나는 오히려 코드 간 스페이스가 늘어나서 더 지저분해보인다.

오히려 디버깅할때 메서드 이름이 없어서 어디서 에러발생했는지 못찾을 때가 많다. <-- 개짜증남

대충 GPT와 대화해본 결과론 이런 설명을 해준다.

익명 메서드 또는 람다 식(lambda expression)은 C#과 같은 프로그래밍 언어에서 매우 유용한 기능입니다. 람다 식은 짧은 함수를 간결하게 표현할 수 있게 해주며, 특히 LINQ 쿼리나 이벤트 핸들러와 같은 경우에 매우 유용합니다. 

여러분이 언급한 대로, 람다 식은 간단한 경우에는 복잡해 보일 수 있고, 항상 필요한 것은 아닙니다. 그러나 람다 식이 유용한 몇 가지 상황을 고려해볼 수 있습니다:

  1. 코드 간결성: 람다 식을 사용하면 작은 함수나 동작을 한 줄로 표현할 수 있어 코드가 더 간결해집니다. 특히, 이벤트 핸들러나 작은 콜백 함수를 작성할 때 유용합니다.
  2. 클로저(Closure) 기능: 람다 식은 외부 변수에 접근할 수 있는 클로저를 생성합니다. 이는 람다 식이 정의된 범위의 변수들을 "캡처"하여 사용할 수 있게 해주며, 이는 특정 상황에서 매우 유용합니다.
  3. LINQ와의 연동: LINQ 쿼리를 작성할 때 람다 식은 필수적입니다. 람다 식을 사용하면 데이터 컬렉션을 처리하는 복잡한 쿼리를 간결하고 읽기 쉬운 형태로 작성할 수 있습니다.
  4. 고차 함수(High-order functions) 사용: 람다 식은 메서드에 함수를 인자로 전달하거나 함수에서 함수를 반환할 때 사용됩니다. 이는 함수형 프로그래밍 패러다임을 따르는 경우 특히 유용합니다.

그러나 이 모든 것이 말씀하신 대로, 간단한 로직에 대해 복잡한 람다 식을 사용하는 것이 불필요하게 보일 수 있습니다. 코드의 간결성과 가독성은 상황에 따라 다르게 해석될 수 있으며, 특정 상황에서는 전통적인 방식이 더 적합할 수도 있습니다. 프로그래밍에서 중요한 것은 각 상황에 가장 적합한 도구와 방법을 선택하는 것입니다.

 

아직은 람다 패러다임에 대한 유용성을 느낄 만큼 많이 고민해보고, 경험이 많은 것은 아니라서 잘 모르겠다. 나에게는 그냥 다들 쓰니까 따라썻고, 이미 익숙해져서 그대로 쓴다는 느낌이 더 강하다. 아! 하나 편한건 있다. VS Code에서 FastArrow라는 익스텐션이 있는데 그걸로 쓰면 함수 선언이 fa + Tab한번으로 되서 편하다 ㅋㅋㅋ 근데 이건  function 선언 단축키 지정하면 되는라서 글쎄...?

그래도 LINQ라는 기능을 쓰려면 필수라고 하니 그때가서 써봐야겟다. 

생각이 바뀐다면 잡생각 정리글로 총정리하겠다.

더보기

++ 어차피 한번만 쓰고 다시는 안쓸 함순데 그냥 이렇게 쓰고말지

//람다 안쓴 덧셈 계산

using System;

class MainClass {
	public static void Main(string[] args) {
    	int a, b;
        a = 1, b = 2;
        Console.WriteLine(a + b);
    }
}

var 자료형

자바스크립트에서 제공하는 let, C++에서의 Auto 키워드와 유사하다, 컴파일러가 자동으로 자료형을 지정해주기 때문에 변수를 선언할 때 개발자는 아무것도 신경쓰지 않아도 되서 매우 편리하다.

C# 4.0

ddynamic 자료형

자바스크립트에서 제공하는 var과 동일하다, C# var과는 다르게 자동으로 자료형을 지정해주기 때문에 변수를 선언할 때 개발자는 아무것도 신경쓰지 않아도 되서 매우 편리하다.

하지만 자료형 변환을 허용하기 때문에 같은 변수 이름을 사용했다면 언제 어디서 자료형이 바뀌었는지 파악하기 힘들어지고, 변수의 스코프도 일일이 계산해야 하는 번거로움이 생겨서 let이라는 자료형이 탄생했다. 요새 프론트엔드 트렌드(react)를 보면 아예 자료형이 변하지 말라고 const로 고정해버리는 것을 생각하면 그냥 dynamic은 쓰지 말고, 한번 초기화 된 뒤에는 자료형이 변하지 않는 var를 사용하는 습관을 들이자.

++번외로 var을 사용하는 경우가 하나 있다.

바로 제네릭 클래스를 사용해 인스턴스 요소가 매번 변경 될 가능성이 있을 때, 이 인스턴스들에 대해 데이터를 순회 해야 하는 경우에 한하여 쓰는것이 좋다고 생각한다. (ex) for (var i in arr))

명명된 매개변수

매개변수를 전달할 때 순서에 맞지 않아도 전달 타입과, 전달되는 갯수가 일치한다면 자동으로 대입연산을 수행한다.

class human {
	public string name;
    public string gender;
    public int age;
    
    public human(string name, string gender, int age) {
    	this.name = name;
        this.gender = gender;
        this.age = age;
	}
}

class MainClass {
	public static void Main(string[] args) {
    	//일반적인 매개변수 전달
    	human man = new human("영수", "man", 23);
        // 명명된 매개변수 전달
        human woman = new human(age:23, name:"영희", gender:"woman");
    }
}

선택적 매개변수

매개변수를 전달할 때 매개 변수를 부족하게 전달하더라도 default 초기화 인자를 대신 전달해 대입연산을 수행한다.

class human {
	public string name;
    public string gender;
    public int age;
    
    public human(string name = "human", string gender = "no Data", int age = 0) {
    	this.name = name;
        this.gender = gender;
        this.age = age;
	}
}

class MainClass {
	public static void Main(string[] args) {
    	human man = new human("영수", "man");
        human woman = new human("영희");
    }
}

C# 5.0

비동기 프로그래밍 async/await

프로그램의 작업 순서를 효과적으로 제어하고, 동시에 수행되는 여러 작업들을 관리할 수 있게 해준다.

두 키워드를 서로 상호보완적으로 작동시켜 병렬 연산의 장점을 사용할 수 있다. 

async

async모듈은 비동기 프로그래밍의 핵심으로 쓰레드와 같이 여러 작업을 수행하는 동안 1. 시간이 오래 걸리는 작업이 완료될 때 까지 기다린 후 2. 다음 작업을 수행하는 것이 아니라, 1과 2를 동시에 수행하는 것을 말한다. 

await

await모듈은 async와는 반대로 특정 작업이 완료될 때 까지 기다리는 것을 말한다. 이것은 메서드 수행시 사전에 필요한 데이터들이 정상적으로 전달되는것을 보장하여 에러처리 로직에 효과적으로 쓰인다.

호출자 정보

프로그램의 작업 도중 에러가 발생했을 때 어떤 호출자에서 에러를 발생시켰는지를 알려주는 기능이다. IDE의 디버거 기능을 좀 더 간소화 하여 제공하는 것 같다. 

using System;
using System.Runtime.CompilerServices;

class Logger
{
    public static void Log(string message,
    [CallerMemberName] string memberName = "",
    [CallerFilePath] string filePath = "",
    [CallerLineNumber] int lineNumber = 0)
    {
        Console.WriteLine($"Message: {message}");
        Console.WriteLine($"Member Name: {memberName}");
        Console.WriteLine($"File Path: {filePath}");
        Console.WriteLine($"Line Number: {lineNumber}");
    }
}

internal class Program
{
    static void Main(string[] args)
    {
        MyMethod();
    }

    private static void MyMethod()
    {
        Logger.Log("Something happened");
    }
}

C# 6.0

정적 메서드 바로 호출하기

using Static 키워드를 통해 동작되는 C++에서의 using namespace를 통한 클래스명 생략과 비슷한 기능이다. 

주의할 점은 C#은 네임스페이스가 다르다면 중복메서드를 허용하기 때문에 메서드의 사용 영역 구분을 잘 해줘야 한다는 것이다. 보통 System이나 표준 라이브러리 관련해서만 생략해서 쓴다.

자동구현 메서드의 초기화

기존에는 getter, setter메서드, 생성자를 자동 생성하는것만 가능했다. (특정 기본값이 들어감)

C# 6.0부터는 이 자동 구현 메서드(생성자단위)에서 default 값을 전달하여 초깃값을 주는것(사용자 지정 기본값)이 가능해졌다. 

class Person
{
    public string Name { get; set; } = "Unknown";
    public int Age { get; set; } = 0;
}

 

람다식 => 의 연산자화

자동 구현 메서드의 초기화와 함께 람다식을 사용하여 자동구현메서드를 간소화 하여 표현할 수 있도록 했다.

기존의 getter/setter

exCode

람다 getter/setter

private string _name;
public string Name
{
    get => _name;
    set => _name = value;
}

문자열보간(Template문자열)

JS에서 사용하던 템플릿문자열, Python의 문자열 포매팅과 같다. 다만 다른 점은 ``키워드가 없이 사용할 수 있다는 점이다.

문자열 내부에 수식을 통해 출력 문자열을 제어할 수 있다.

string name = "Alice";
int age = 30;
string message = $"Hello, my name is {name} and I am {age} years old.";
Console.WriteLine(message);

C# 7.0

튜플

리턴문에서 여러개의 프로퍼티를 전달하기 위해 도입된 새로운 변수

패턴 매칭

is와 switch문을 사용해 패턴을 매칭해 타입의 확인과 캐스팅을 간단하게 만드는 일종의 람다 

로컬 함수

함수 안의 함수 선언이다.

참조변수와 참조반환

유사 Pointer

예제 코드들은 아래 링크에 있습니다.

Workspace/C#/historyOfCSharp at main · cyphen156/Workspace (github.com)

 

 

 

 

 

 

'프로그래밍 > C#' 카테고리의 다른 글

C# 실습 1 .NET 8 Winforms 앱 만들기  (0) 2023.11.30
C#에서 클래스와 namespace(C++와 다른점 2)  (0) 2023.11.29
C# 기초2 C/C++와 다른점  (2) 2023.11.28
C# 기초1  (0) 2023.11.28