extern

extern 키워드를 사용하면 플랫폼 간 상호 운용성, 성능 최적화, 코드의 안전성과 에러 처리를 모색할 수 있다.

 

플랫폼 간 상호 운용성

C# 언어를 사용하는 프로젝트에서 이외의 언어로 작성된 라이브러리 함수를 호출하는 경우는 아주 흔하게 발생하는 상황이다. 예를 들어서 C#에서 Windows API를 호출하려면 DllImport 속성을 사용해서 C# 코드에서 네이티브 코드 함수를 선언해야 한다. 이를 통해 C# 프로그램에서 운영 체제 수준의 다양한 기능을 직접적으로 사용할 수 있다.

 

Windows API 중 몇 가지 간단한 함수를 사용해 본다.

namespace Test
{
    using System;
    using System.Text;
    using System.Runtime.InteropServices;

    internal class Program
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct SYSTEMTIME
        {
            public ushort Year;
            public ushort Month;
            public ushort DayOfWeek;
            public ushort Day;
            public ushort Hour;
            public ushort Minute;
            public ushort Second;
            public ushort Milliseconds;
        }

        [DllImport("Kernel32.dll")]
        public static extern bool SetConsoleTitle(string title);
        

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern uint GetConsoleTitle(StringBuilder buffer, uint size);

        [DllImport("Kernel32.dll")]
        public static extern void GetSystemTime(out SYSTEMTIME st);

        static void Main(string[] args)
        {
            SetConsoleTitle("Bak's Console");
            Console.WriteLine("Hello, World!");

            StringBuilder buffer = new StringBuilder(256); // Define buffer size according to your need
            GetConsoleTitle(buffer, (uint)buffer.Capacity);
            Console.WriteLine("Console Title: " + buffer.ToString());

            SYSTEMTIME st;
            GetSystemTime(out st);
            Console.WriteLine("Current System Time: {0}-{1}-{2} {3}:{4}:{5}.{6}",
                st.Year, st.Month, st.Day, st.Hour, st.Minute, st.Second, st.Milliseconds);

            Console.ReadLine();
        }
    }
}

 

실행 결과

C# - extern kernel32.dll

 

외부 함수 SetConsoleTitle을 사용해서 실행되는 콘솔의 타이틀을 변경하고 GetConsoleTitle을 사용해서 변경한 콘솔의 타이틀을 가지고 와서 콘솔에 찍어본다. 그리고 GetSystemTime을 사용해서 현재 실행 중인 장치의 시간을 가지고 온다.

 

코드를 보면 알 수 있듯이 필요한 기능을 사용하기 위해서는 어트리뷰트를 선언할 때 추가로 필요한 값들이나 사용하고자 하는 함수의 반환값, 필요한 파라미터 등에 대한 정보들이 필요하다.

 

이러한 정보들은 마이크로소프트 공식 문서나 또는 이를 주제로 하는 커뮤니티에서 확인할 수 있고 이외 라이브러리들도 제공하는 곳에서 API 문서를 확인할 수 있다.

 

성능 최적화

C# 코드 내에서 수학 계산이나 이미지 처리와 같은 고성능의 연산을 요구하는 네이티브 라이브러리 함수를 호출하는 방법이 있다. 이러한 방식은 매니지드 코드(Managed Code)에 비해 실행 속도가 빠른 네이티브 코드의 이점을 활용할 수 있게 해 준다.

 

네이티브 코드를 사용하는 것이 언제나 성능적으로 이점이 있다고 할 수는 없으므로 해당 목적을 위해서 외부 라이브러리를 사용한다고 했을 때에는 .Net 환경에서 이미 최적화된 상태인 C# 라이브러리의 성능과 비교해서 네이티브 코드를 사용했을 때에 성능적인 이점이 있을 때 사용하는 것이 좋다.

 

 

728x90
반응형

new

new 키워드의 사용을 최소화하여 객체 생성의 통제를 개선하고 코드의 유연성과 재사용성을 높여 의존성 관리를 용이하게 할 수 있다. 이를 통해서 메모리 사용을 최적화하고 객체의 생명주기를 효과적으로 관리할 수 있다.

 

디자인 패턴(Design Pattern)

주로 사용되는 방식들은 패턴으로 정형화된 구조를 만들 수 있다. 

자주 그리고 반복적으로 사용되는 코드의 디자인을 정형화된 패턴으로 만들 수 있는데 이를 디자인 패턴이라고 한다.

 

디자인 패턴은 다양한 종류들이 존재하는데 이번에는 그중 객체의 생성과 할당과 관련된 패턴들 중 대표적인 몇 가지만 살펴본다.

 

싱글턴 패턴(Singleton Pattern)

싱글턴 패턴은 클래스의 인스턴스가 하나만 존재하도록 보장하는 패턴이다.

이 패턴은 객체를 전역적으로 접근할 수 있게 하여 리소스에 대한 중복 접근을 방지하고 메모리 사용을 효율적으로 관리할 수 있도록 한다. 시스템 전체에 걸쳐 일관된 상태를 유지할 수 있으며 객체의 생성과 생명주기를 통제할 수 있다.

 

public class Singleton
{
    private static Singleton instance;
    private Singleton() {}
    
    public static Singleton GetInstance()
    {
        if (instance == null)
        {
            instance = new Singleton();
        }
        return instance;
    }
}

 

생성자를 private으로 선언하여 클래스 외부에서는 새 객체를 생성할 수 없도록 하며 GetInstance()를 통해서 유일하게 존재하는 클래스의 인스턴스에 접근할 수 있도록 한다.

 

팩토리 메서드 패턴(Factory Method Pattern)

객체 생성을 처리하는 인터페이스를 제공하면서 서브 클래스가 생성할 객체의 클래스를 지정할 수 있게 한다.

이 패턴을 통해서 객체 생성을 추상화할 수 있으며 시스템의 확장성과 유연성을 향상할 수 있다.

 

팩토리 패턴의 방법은 다음과 같다.

 

1. 생성할 객체들이 공통적으로 구현할 인터페이스를 정의한다. 

2. 인터페이스를 구현하는 클래스들을 생성한다. 각 클래스에서는 인터페이스의 메서드를 구체적으로 구현한다.

3. 객체 생성을 담당할 메서드를 포함하는 인터페이스를 정의한다. 이 메서드는 인터페이스 타입의 객체를 반환한다.

4. 생성자 인터페이스를 구현하는 클래스를 생성하고 팩토리 메서드를 오버라이드하여 인스턴스를 생성하고 반환한다.

5. 외부에서는 팩토리 메서드를 통해 객체를 생성하여 의존성 없이 동작할 수 있게 된다.

 

예시로 여러 종류의 몬스터를 만드는 팩토리 패턴을 활용하여 작성해 본다.

 

// 몬스터 인터페이스 정의
public interface IMonster
{
    void Attack();
}

// 구체적인 몬스터 클래스
public class Zombie : IMonster
{
    public void Attack()
    {
        Console.WriteLine("Zombie attacks!");
    }
}

public class Vampire : IMonster
{
    public void Attack()
    {
        Console.WriteLine("Vampire attacks!");
    }
}

// 몬스터 생성을 위한 팩토리 클래스
public abstract class MonsterFactory
{
    public abstract IMonster CreateMonster();
}

public class ZombieFactory : MonsterFactory
{
    public override IMonster CreateMonster()
    {
        return new Zombie();
    }
}

public class VampireFactory : MonsterFactory
{
    public override IMonster CreateMonster()
    {
        return new Vampire();
    }
}

class Program
{
    static void Main(string[] args)
    {
        MonsterFactory factory = new ZombieFactory();
        IMonster monster = factory.CreateMonster();
        monster.Attack();

        factory = new VampireFactory();
        monster = factory.CreateMonster();
        monster.Attack();
    }
}

 

 

특히 다양한 객체가 동일한 인터페이스를 공유하거나 시스템 구성 요소 간의 결합도를 낮추기 원할 때 유용하며 객체의 생성과 클래스의 구현을 분리함으로써 모듈화 된 코드 구조를 가능하게 한다.

 

빌더 패턴 (Builder Pattern)

빌더 패턴은 복잡한 객체의 생성과정을 단계별로 나누어 구축하는 디자인 패턴으로 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 다양한 표현 결과를 얻을 수 있도록 한다. 주로 복잡한 객체를 조립해야 할 때 사용되며 각 부분의 조립 순서를 외부에서 지정할 수 있다.

 

// 캐릭터의 인터페이스 정의
public interface ICharacter
{
    void EquipWeapon(string weapon);
    void EquipArmor(string armor);
    void AddAbility(string ability);
}

// 캐릭터 빌더 인터페이스
public interface ICharacterBuilder
{
    ICharacterBuilder BuildWeapon(string weapon);
    ICharacterBuilder BuildArmor(string armor);
    ICharacterBuilder BuildAbility(string ability);
    Character Build();
}

// 구체적인 캐릭터 빌더
public class HeroBuilder : ICharacterBuilder
{
    private Character _character = new Character();

    public ICharacterBuilder BuildWeapon(string weapon)
    {
        _character.EquipWeapon(weapon);
        return this;
    }

    public ICharacterBuilder BuildArmor(string armor)
    {
        _character.EquipArmor(armor);
        return this;
    }

    public ICharacterBuilder BuildAbility(string ability)
    {
        _character.AddAbility(ability);
        return this;
    }

    public Character Build()
    {
        return _character;
    }
}

// 사용 예
public class Game
{
    public void CreateHero()
    {
        HeroBuilder builder = new HeroBuilder();
        Character hero = builder.BuildWeapon("Sword")
                                 .BuildArmor("Shield")
                                 .BuildAbility("Invisibility")
                                 .Build();
    }
}

 

 

캐릭터가 가지는 공통적인 기능을 인터페이스로 구현한 다음 구체적인 캐릭터 클래스에서 원하는 옵션으로 조립할 수 있도록 단계를 구분한다.

728x90
반응형

new

메모리 할당

new 키워드를 사용하면 CLR은 관리되는 힙에 객체를 위한 메모리를 할당한다. 

할당된 메모리는 해당 객체에 대한 모든 참조가 없어질 때까지 메모리에 존재하고 더 이상 참조되지 않을 때 GC에 의해 관리된다.

public class MyClass
{
    public int Number { get; set; }
}

public class Program
{
    public static void Main()
    {
        MyClass myObject = new MyClass();
        myObject.Number = 1;
        Console.WriteLine(myObject.Number);
    }
}

 

 

생성자

생성자는 객체가 할당될 때 호출되는 생명주기 메서드(Lifecycle Methods)이다.

클래스의 객체를 생성하는 방식을 생각해 보면 new 키워드 뒤에 클래스명의 함수를 쓰는데 이 함수는 클래스에서 따로 작성하지 않았는데도 문제없이 컴파일된다.

 

MyClass myObject = new MyClass();

 

그 이유는 클래스에 별도로 생성자에 대한 작성을 하지 않아도 컴파일 단계에서 자동으로 기본 생성자를 만들어서 사용하기 때문에 컴파일 에러가 발생하지 않게 된다.

 

기본 생성자의 형태는 클래스 명의 메서드로 함수 내부에는 비어있는 형태로 볼 수 있다.

 

public class MyClass
{
    public int Number { get; set; }
    // 기본 생성자 형태
    public Number(){}
}

public class Program
{
    public static void Main()
    {
        MyClass myObject = new MyClass();
        myObject.Number = 1;
        Console.WriteLine(myObject.Number);
    }
}


생성자의 접근제한자를 private 등을 사용해서 클래스의 인스턴스 생성을 제한할 수 있다.

 

생성자는 파라미터를 추가한 형태로 선언이 가능하며 클래스의 인스턴스가 생성될 때 필드나 프로퍼티를 초기화하는 방법으로 사용할 수 있다.

 

public class MyClass
{
    public int Number { get; set; }
    private string _id;
    private string _pw;
    private int _age;
    private bool _isAgree;
    
    // 기본 생성자 형태
    public Number(string id, string pw, int age, bool isAgree)
    {
    	_id = id;
        _pw = pw;
        _age = age;
        _isAgree = isAgree;
    }
}

public class Program
{
    public static void Main()
    {
        MyClass myObject = new MyClass("bak", "****", 19, true);
        // 에러 발생
        MyClass myObject = new MyClass();
    }
}

 

이렇게 하면 인스턴스를 생성과 동시에 필드값을 초기화할 수 있다.

주의할 점은 이렇게 파라미터를 받는 생성자를 작성하게 되면 컴파일러에서는 더 이상 기본 생성자는 만들어 주지 않기 때문에 기본 생성자를 사용하려고 하면 컴파일 에러가 발생하게 되므로 기본 생성자도 사용하기를 원하면 추가로 작성해야 한다. 즉 생성자도 오버로딩이 가능하기 때문에 다양한 매개변수를 받는 생성자의 선언이 가능하다.

 

소멸자

생성자와는 호출 시점에만 차이가 있는 생명주기 메서드이다. (파괴자라고도 한다.)

소멸자는 객체가 소멸되는 시점에 호출되며 C#에서는 더 이상 객체가 참조되는 곳이 없을 때 GC에 의해서 관리되어 소멸될 때 소멸자가 호출된다.

 

따라서 소멸자의 호출 시점이 명확하지 않으므로 소멸자 내부에서 어떠한 기능을 수행시키게 한다면 동작에 대한 기대가 힘들기 때문에 C#에서는 이를 활용하는 경우는 드물다. 

 

public class MyClass
{
    public Number(){}
    // 소멸자 선언
    ~Number(){}
}

 

소멸자는 직접 호출도 불가능하기 때문에 별도의 접근 제한자를 지정할 필요도 없다.

728x90
반응형

Collections

. Net Framework에서 사용되던 라이브러리이다. Generic 기능이 도입되기 이전에 사용되었으며 Collections의 클래스들은 모든 요소를 object 타입으로 처리하여  요소를 다룰 때에 형변환을 필요로 한다.

 

대표적의로 ArrayList, Hashtable, sortedList, Stack, Queue 등이 있다.

 

Generic

Collections의 클래스들은 형변환이 필요하기 때문에 잘못된 타입을 사용할 경우 에러가 발생하게 된다. 

이 문제를 해결하기 위해서 안정성을 제공하는 새로운 클래스들이 Generic 네임스페이스로 추가되었다.

 

대표적으로 List, Dictionary, Queue <T>, Stack <T> 등이 있다.

 

 

Collections Generic
ArrayList List<T>
Hashtable Dictionary<TKey, TValue>
Queue Queue<T>
Stack Stack<T>

 

Compare

옛날부터 작성된 코드인 경우에는 Collections 라이브러리를 사용하기 위해서 해당 네임스페이스를 선언해 주는 경우가 많다. 거기다 새로 추가된 Generic도 사용하기 위해서 두 라이브러리를 모두 선언하는 경우가 많은데 공식 문서에 따르면 Collections에서 사용하는 클래스들은 Generic에 안정성이 추가된 대체할 수 있는 클래스들이 있기 때문에 되도록이면 Generic만 사용하기를 권장한다.

 

대표적으로 ArrayList와 List<T>를 비교해 본다. 

using System;
using System.Collections;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // ArrayList
        ArrayList arrayList = new ArrayList();

        // 요소 추가
        arrayList.Add("Hello");
        arrayList.Add(30);
        arrayList.Add(true);

        // 요소 접근
        string str = (string)arrayList[0];
        int num = (int)arrayList[1];
        bool flag = (bool)arrayList[2];

        Console.WriteLine(str);  // 출력: Hello
        Console.WriteLine(num);  // 출력: 30
        Console.WriteLine(flag); // 출력: True
        
        // List<T>
        List<object> list = new List<object>();

        // 요소 추가
        list.Add("Hello");
        list.Add(30);
        list.Add(true);

        // 요소 접근
        string str = list[0];
        int num = list[1];
        bool flag = list[2];

        Console.WriteLine(str);  // 출력: Hello
        Console.WriteLine(num);  // 출력: 30
        Console.WriteLine(flag); // 출력: True
    }
}

arrayList와 list 변수 모두 가변 배열로 요소를 추가하고 인덱스로 접근하여 값을 사용하고 있다.

하지만 사용법에서 차이가 발생한다.

 

ArrayList는 Add 메서드를 사용하여 요소를 추가하고 접근할 때에는 형변환을 수행해야 한다. 모든 요소를 object 타입으로 처리하므로 요소를 사용하기 전에 타입을 캐스팅해야 한다.

 

하지만 List 요소의 타입은 컴파일 시점에 검사 되기 때문에 추가한 요소에 접근할 때 형변환이 따로 필요하지 않다. 

즉 Generic을 사용하게 되면 타입의 안정성이 보장되고 컴파일러가 타입을 검사를 수행하기 때문에 타입으로 인해 발생하는 문제를 사전에 발견할 수 있으며 형변환 코드를 작성할 필요가 없기 때문에 코드가 간결해진다.

728x90
반응형

C#은 객체지향 언어로 객체지향 프로그래밍을 위한 개념들이 있다.

 

Class

객체를 정의하는 템플릿이며 객체의 상태를 나타내는 필드와 동작을 나타내는 메서드를 포함한다.

즉 클래스는 객체를 표현하기 위한 설계도로 볼 수 있다.

public class MyClass
{
    // field, 멤버 변수
    public int id;
    public string name;
    public int age;
    
    // method, 멤버 함수
    public void Introduc()
    {
        string introduce = "My age : " + agem + "\nMy name : " + name;
    	Cosole.WriteLine(introduce);
    }
}

MyClass라는 객체에는 id, name, age 멤버변수와 Introduce 메서드를 포함하고 있다.

 

Instance

클래스의 인스턴스로 실제로 메모리에 할당된 데이터이다. 객체는 클래스에서 정의된 필드와 메서드를 사용할 수 있으며 개별적인 상태를 유지한다.

public void Main(string[] args)
{
    // Instance, MyClass 클래스의 객체
    MyClass myClass = new MyClass();
    myClass.id = 1;
    myClass.name = "bak";
    myClass.age = 30;
    myClass.Introduce();
    
    // Instance, myClass와는 개별적인 객체
    MyClass myClass2 = new MyClass();
    myClass2.id = 2;
    myClass2.name = "kim";
    myClass2.age = 25;
    myClass2.Introduce();
}

동일한 MyClass를 인스턴스화한 myClass와 myClass2는 개별적인 객체이다.

 

Inherit

클래스 간의 계층 구조를 형성하여 코드의 재사용성과 구조화를 실현한다. 기존 클래스를 기반으로 새로운 클래스를 정의하고 확장할 수 있는데 ㅅ상속을 통해 부모 클래스의 멤버를 자식 클래스에서 사용하는 것이 가능하다.

 

public class Parent
{ 
    public int id;
    public string name;
    
    public void MethodParent()
    {
    	Console.Log(id, name);
    }
}

public class Child
{
    public void MethodChild()
    {
    	id = 10;
        name = "bak";
        MethodParent();
    }
}

재사용할 코드는 부모 클래스로 만들어서 자식 클래스에서 상속하여 그대로 사용할 수 있다.

 

Polymophism

다형성은 같은 이름의 메서드나 속성을 다른 방식으로 구현하는 개념이다. 다형성은 상속과 관련이 깊으며 부모 클래스의 타입으로 자식 클래스의 객체를 참조하면 다형성을 활용할 수 있다.

 

public class Parent
{
    public virtual void MethodParent()
    {
    	Console.WriteLine("Call Parent Method");
    }
}

public class Child
{
 	public override void MethodParent()
    {
    	Console.WriteLine("Call Parent Method From Child");
    }
}

다형성의 대표적인 예는 부모 클래스의 메서드를 자식에서 오버라이딩하는 경우가 있다.

 

Encapsulation

객체의 상태와 동작을 외부로부터 감추는것을 뜻한다.  클래스는 필드와 메서드를 적절한 접근 제한자로 제어하여 캡슐화를 구현할 수 있다. 이는 객체의 내부 구현을 보호하며 외부에서는 필요한 기능만 사용할 수 있도록 한다.

 

public class MyClass
{
    private string name;
    private int age;
    
    public void SetName(string name)
    {
    	this.name = name;
    }
    
    public string GetName()
    {
    	return name;
    }
    
    public void SetAge(int age)
    {
    	this.age = age;
    }
    
    public int GetAge()
    {
    	return age;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
    	MyClass myClass = new MyClass();
        myClass.SetName("Bak");
        myClass.SetAge(30);
        Console.WriteLine("Name : " + myClass.GetName());
        Console.WriteLine("Age : " + myClass.GetAge());
    }
}

클래스에서 접근을 허용할 부분을 public 불필요한 부분은 private로 접근 제한자를 두어 필요한 부분만 제어할 수 있도록 한다.

 

Abstract

복잡한 시스템을 단순화하고 핵심적인 요소만을 추출하여 모델링하는 과정을 말한다. 추상화는 클래스의 공통적인 특징을 추출하여 부모 클래스나 인터페이스로 정의할 수 있다.

 

public class Person
{
    public int age;
    public string name;
    public int countryCode;
    
    public virtual void Eat() {}
    public virtual void Sleep() {}
    public virtual void Work() {}
}

public class Student : Person
{
    public override void Work()
    {
    	Console.WirteLine("Studying");
    }
}

public class Teacher : Person
{
    public override void Work()
    {
    	Console.WriteLine("Teaching");
    }
}

추상화는 클래스를 만들 때 필요한 기본 개념으로 재사용, 다형성 등을 고려하여 최대한 구체적이지 않으면서 공통적인 부분을 부모 클래스로 만들 때 필요하다.

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# 인스턴스 생성 키워드 : new - 생성자와 소멸자  (0) 2024.05.06
C# 라이브러리 Collections, Generic  (0) 2023.05.16
C# Operator 키워드  (0) 2023.04.27
C# Method Parameter 키워드  (0) 2023.04.27
C# Namespace 키워드  (0) 2023.04.27

is

주어진 값이 특정 클래스 혹은 구조체의 인스턴스인지 아닌지를 판별하는 연산자이다.

즉, is 연산자는 해당 값이 주어진 클래스 또는 구조체로부터 상속되었거나 해당 구조체와 일치하는지를 판별하는 데 사용된다.

 

object obj = "Hello World!";
if (obj is string)
{
	Console.WriteLine("obj is a string");
}

 

is 키워드는 obj 변수가 string 타입에 해당하는지 검사한다.

만약 obj가 string 타입이면 로그가 출력되는데 string은 object의 파생 클래스이기 때문에 object 형식으로 형변환이 가능하며 해당 조건문은 true로 로그가 출력이 된다.

 

as

참조 타입의 변수에서 형식 변환을 수행하는 연산자이다.

as 연산자를 사용하면 형식 변환 작업을 수행하면서 실패한 경우 null을 반환한다.

 

object obj = "Hello World!";
string str = obj as string;
if (str != null)
{
	Console.WriteLine(str,ToUpper());
}
else
{
	Console.WriteLine("obj is not a string");
}

 

object 타입인 obj를 as를 사용해서 string으로 형변환을 하였다. 

당연히 object는 string으로 형변환이 가능하기 때문에 해당 결과는 true로 결과가 출력된다.

 

is 만 사용할 경우 암시적으로 형변환이 일어나는데 이때 예외처리 에러가 발생할 수 있다. as를 사용해서 형변환을 시도한 결과를 null 예외처리를 할 수 있기 때문에 is와 as는 함께 많이 사용된다.

 

sizeof

피연산자의 크기를 바이트 단위로 계산하여 반환하는 연산자이다.

이 연산자는 컴파일 타임에 실행되며 모든 유형과 식을 피연산자로 취급이 가능하다.

// sizeof(type)
int size = sizeof(int);

sizeof 연산자는 스택에 메모리를 할당할 때 유용하다. 정확한 크기를 알고 있는 배열을 만들 때 sizeof 연산자를 사용하여 각 요소의 크기를 계산하고 이를 기반으로 배열의 크기를 계산할 수 있다.

 

하지만 sizeof 연산자는 값 형식의 객체에 대해서만 사용할 수 있으며 참조 형식에 대해서는 사용이 불가능하다.

참조 타입의 경우 인스턴스 크기를 얻기 위해서는 Marshal.Sizeof() 메서드를 사용할 수 있지만 이 메서드는 해당 인스턴스가 저장된 메모리의 크기를 반환하므로 실제 인스턴스의 크기와는 다를 수 있다.

 

typeof 

특정 타입의 System.Type 객체를 반환하는데 이를 통해 코드에서 지정한 타입에 대한 정보를 얻을 수 있다. 

컴파일 타임에 타입을 검사하기 때문에 코드의 안정성과 가독성을 높일 수 있으며 객체의 타입을 확인하고 이에 따라 적절한 작업을 수행하는 코드를 작성할 수 있다.

 

// typeof(Type)
Type t = typeof(int);

해당 타입의 System.Type 객체를 반환한다. C#에서 사용되는 모든 타입에 대한 정보를 담고 있으며 해당 타입에 대한 모든 메타데이터를 포함하고 있다.

 

t 변수는 int 타입의 System.Type 객체를 참조하게 되며 이를 통해 int 타입에 대한 정보를 얻을 수 있다.

이외에도 다양한 타입에 대한 정보를 typeof 연산자를 통해 얻을 수 있다.

 

 

 

stackalloc

고정 크기의 메모리 블록을 동적으로 할당하기 위해서 사용한다.

메모리 할당과 해제가 매우 빠르고 GC에 의해서 관리되는 힙 메모리를 사용하지 않으므로 애플리케이션의 성능을 향상할 수 있다. C/C++의 alloca() 함수와 비슷한 역할을 한다. 

 

하지만 alloca과 다르게 stackalloc은 호출된 함수의 실행이 종료될 때 자동으로 메모리가 해제되게 된다.

 

Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5 };
var idx = numbers.IndexOfAny(stackalloc[] { 2, 4, 6, 8 });
Console.WriteLine(idx);

 

포인터 형식으로도 메모리 할당이 가능하다.

 

unsafe
{
	int length = 3;
    int* numbers = stackalloc int[lenth];
    for (var i = 0; i < length; i++)
    {
    	numbers[i] = i;
    }
}

포인터 형식을 사용할 경우 지역 변수에서만 stackalloc을 사용하여 변수를 초기화 할 수 있다.

 

루프 내부에서는 stackalloc을 사용하지 않아야하며 루프 외부에서 메모리 블록을 할당하고 루프 내부에서 사용하는 방식으로 가야 한다.

 

스택에서 사용 가능한 메모리는 제한적이기 때문에 너무 많은 메모리를 할당하게 되면 스택오버플로우 에러가 발생한다.

이 문제를 방지하기 위해서는 할당하려는 버퍼의 크기가 특정 한도 내에서만 할당되도록 제한을 두어야 한다.

const int MaxStackLimit = 1024;
Span<byte> buffer = inputLength <= MaxStackLimit ? 
					stackalloc byte[inputLength] : new byte[MaxStackLimit];

입력한 버퍼의 크기가 최대 크기보다 작으면 입력한 만큼의 버퍼를 할당하고 최대를 벗어날 경우에는 최대크기만큼만 버퍼를 할당하도록 한다.

 

새로 할당된 메모리는 반드시 사용 전에 초기화가 필요하다. 이때 모든 형식의 기본값으로 설정할 수 있는 Span<T>.Clear 메서드를 사용할 수 있다.

 

 

checked/unchecked

정수 연산 시 overflow 발생 여부를 처리하는 데 사용한다.

기본적으로 C#에서 정수형 연산은 오버플로우가 생기면 예외가 발생한다. 이 예외를 처리하기 위해서는 try-catch 문을 사용해야 하는데 이는 코드를 복잡하게 만들 수 있기 때문에 이런 상황에서는 checked와 unchecked 키워드를 사용할 ㅅ ㅜ있다.

 

int x = int.MaxValue;
int y = 1;

checked
{
	int z = x + y;
}

unchecked
{
	int z = x + y;
}

 위 코드는 int의 최댓값이 1을 더하는 것으로 OverflowException이 발생하는 코드이다.

이때 checked를 사용하면 계산과정에서 overflow가 나기 때문에 예외가 발생한다.

unchecked를 사용할 경우에는 overflow가 발생해도 예외를 발생시키지 않고 결과를 진행한다.

int a = int.MaxValue;
int b = unchecked(a + 1);
int c = checked(a + 1); // System.OverflowException 예외 발생

 

 

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# 라이브러리 Collections, Generic  (0) 2023.05.16
C# 객체지향 프로그래밍  (0) 2023.05.15
C# Method Parameter 키워드  (0) 2023.04.27
C# Namespace 키워드  (0) 2023.04.27
C# 멤버 명시 키워드 : this  (0) 2023.04.25

params

메서드의 매개 변수로 배열을 받을 수 있게 해주는 키워드이다.

params 키워드를 사용하면 메서드 호출 시 각각의 인자를 전달해서 배열형식으로 전달할 수 있다.

 

만약 배열이나 리스트와 같은 타입을 인수로 전달해야 하는 경우 리스트나 배열로 선언해서 값을 넣어줘야 한다.

public void Main(string[] args)
{
	int[] arrI = new int[4]{1,2,3,4}
    MyMethod(arrI);
}

public void MyMethod(int[] values){}

 

 

이럴 때 호출할 함수의 매계변수를 params로 선언하면 따로 배열로 묶어줄 필요가 없어진다.

 

public void Main(string[] args)
{
	MyMethod(1, 2, 3, 4);
}

public void MyMethod(params int[] values)
{
	foreach (int value in values)
    {
    	Console.WriteLine(value);
    }
}

 

 

params 키워드를 통해서 인자를 각각의 인덱스에 값을 넣는 형식으로 받아서 사용이 가능하다.

 

ref

reference 즉, 참조를 뜻하는 키워드이다.

이 키워드를 사용해서 값 타입을 참조 타입처럼 사용할 수 있다.

 

static void Swap(int a, int b)
{
	int temp = a;
    a = b;
    b = temp;
}

인자로 받은 두 값을 서로 바꾸는 메서드이다.

int는 값 형식으로 값의 복사만 이루어지기 때문에 위 메서드는 a와 b의 값이 바뀌지 않는다.

 

이때 ref 키워드를 사용하면 참조를 하게 되므로 본래의 변수들의 값이 수정된다,

static void Swap(ref int a, ref int b)
{
	int temp = 4;
    a = b;
    b = temp;
}

이렇게 ref 키워드를 사용해서 메서드를 정의하게 되면 호출하는 곳에서도 ref 키워드를 붙여서 인자를 전달해야 한다.

 

int x = 1;
int y = 2;
Swap(ref x, ref y);

C++의 포인터와 비슷한 개념이다.

하지만 포인터는 메모리 주소를 가리키는 변수이고 ref는 메모리 위치를 직접 가리키는 것이 아닌 해당 객체에 대한 참조를 가리키게 된다.

 

따라서 ref 키워드를 사용하면 참조형식 변수를 함수에 전달하여 함수 내에서 해당 변수가 참조하는 메모리 위치를 직접 조작할 수 있다. 이를 통해 함수 내에서 해당 변수가 가리키는 객체를 직접 수정하는 것이 가능하다.

 

out 

메서드에서 값을 반환하는 것 외에도 메서드 호출 이후에 값을 전달할 수 있는 방법을 제공한다,

즉 out 키워드를 사용하면 메서드 내부에서 매개변수 값을 수정할 수 있고, 수정된 값을 호출한 곳에서 가져다 쓸 수 있다.

 

키워드는 메서드의 매개변수 앞에 붙여서 사용한다. 

public void Calculate(int x, int y, out int sum, out int product)
{
	sum = x + y;
    product = x * y;
}

Calculate 메서드는 x, y 두 매개변수를 받는다. 그리고 out 키워드를 사용해서 sum, product 두 개의 매개변수를 추가로 받고 있다.

 

해당 메서드 내부에서는 전달받은 x, y를 이용해서 sum, product를 계산한 후 각각의 매개변수에 값을 할당한다.

이후 메서드를 호출할 때 sum, product를 담을 변수를 선언하고 이 변수를 매개변수로 넘겨주어야 한다.

 

int a = 5;
int b = 10;
int totalSum = 0;
int totalProduct = 0;

Calculate(a, b, out totalSum, out totalProduct);

Console,WriteLine(totalSum + " " + totalProduct);

 

메서드가 호출되면 Calculate 내부에서는 전달받은 a와 b의 가지고 각각 totalSum의 값과 totalProduct의 값을 계산하여 저장한다. 따라서 함수의 호출 이후에 totalSum과 totalProduct의 값은 계산 결과가 담기게 된다.

 

 

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# 객체지향 프로그래밍  (0) 2023.05.15
C# Operator 키워드  (0) 2023.04.27
C# Namespace 키워드  (0) 2023.04.27
C# 멤버 명시 키워드 : this  (0) 2023.04.25
C# 상위 멤버 접근 키워드 : base  (0) 2023.04.25

Namespace

네임스페이스는 다른 식별자와 구분하기 위한 식별자의 집합을 의미한다.

즉, 클래스, 구조체, 인터페이스, 델리게이트, enum 등의 데이터 형식, 메서드, 변수 등을 구별하기 위한 컨테이너 역할을 한다. 

 

예를 들어 클래스명 같은 경우 일반적으로 흔히 사용하는 이름으로 작명한 경우 다른 패키지를 설치하여서 사용하다 보면 동일한 클래스명으로 인해서 충돌이 발생할 수 있다. 이럴 때 클래스를 자신만의 네임스페이스로 지정해서 구분해 두면 이름이 중복되더라도 namespace.class와 같이 구분해서 접근되기 때문에 에러를 방지할 수 있다.

 

public class MyClass{}

namespace MyScript
{
	public class MyClass{}
}
// 네임스페이스로 인해 동일한 이름의 두 클래스는 구분 된다.

 

MyScript 네임스페이스로 싸여진 MyClass를 사용하기 위해서는 MyScript에서 사용할 것임을 명시해야 한다.

 

using

코드에서 다른 네임스페이스를 가져오기 위해 사용된다.

네임스페이스로 싸인 클래스를 사용하기 위해서는 해당 네임스페이스를 명시한 뒤에 그 내부의 클래스에 접근이 가능하다.

 

namespace MyScript
{
	public class MyClass{}
}

public void Main(string[] args)
{
	MyScript.MyClass() myClass = ...
}

 

만약 네임스페이스로 싸여진 클래스의 사용이 빈번해지게 되면 매번 명시해 주는 것이 번거롭고 코드의 가독성을 떨어뜨리게 된다.

 

이때 using을 통해서 사용할 네임스페이스를 전역으로 선언할 수 있다.

 

using MyScript;

~

public void Main(string[] args)
{
	MyClass myClass = ... // using으로 인해서 MyScript의 MyClass임을 알 수 있다.
}

 

그럼에도 동시에 동일한 클래스명을 가지게 되는 경우에는 코드 앞에 네임스페이스를 명시해주어야 한다.

 

operator

연산자 오버로딩을 정의하는 데 사용되는 키워드이다. 

산술, 비교, 논리 등의 연산자를 재정의하여 다른 연산을 수행하도록 한다. 

예를 들어 내가 만든 타입의 경우 내장 연산자에는 정의되어있지 않기 때문에 계산이 불가능한데 이때 오버로딩을 해서 내가 만든 타입을 연산자로 처리하고 결과를 리턴할 수 있다.

 

public static MyClass operator +(MyClass a, MyClass b)
{
	// add MyClass a and b
    return result;
}

public void Main(string[] args)
{
	MyClass myClass1 = new MyClass();
    MyClass myClass2 = new MyClass();
    
    // 오버로딩한 내용으로 결과를 반환받게 된다.
    MyClass result = myClass1 + myClass2;
}

내가 만든 클래스 타입의 MyClass 두 개를 + 연산하기 위해서 + 연산자를 오버로딩했다.

원하는 결과를 얻기 위해서는 내부에서 필요한 동작을 구현하면 MyClass 결과를 return 받을 수 있다.

 

extern alias

다른 어셈블리를 참조할 때 사용되는 지시어이다.

일반적으로 프로젝트에서는 하나 이상의 어셈블리를 참조해야 하는데 두 개 이상의 어셈블리가 같은 이름을 가지고 있는 경우 이를 구분하기 위해서 extern alias를 사용할 수 있다.

 

extern alias A1;
extern alias A2;

A1::SomeNamespace.SomeClass someObjcet = new A1::SomeNamespace.SomeClass();
A2::SomeNamespace.SomeClass anotherObjcet = new A2::SomeNamespace.SomeClass();

A1과 A2는 각각 어셈블리의 별칭으로 이를 이용해서 어셈블리의 이름을 지정할 수 있다. 이렇게 구분지은 어셈블리는 동일한 이름을 가지고 있더라도 참조할 때 충돌하지 않는다.

 

:: operator

:: 연산자는 extern alias 지시어를 사용해 참조된 어셈블리 내의 형식을 참조하기 위해서 사용한다.

System 네임스페이스에 있는 COnsole 클래스를 사용하고자 할 때 System.Console로 사용할 수 있지만 다른 어셈블리에 있는 System 네임스페이스의 Console을 사용하고자 할 때는 extern alias 지시어를 사용한 구분이 필요하다.

 

extern alias aliasName;
aliasName::System.Console.WriteLine("Hello World");

여기서 aliasName은 다른 어셈블리에서 사용할 alias 이름을 지정하는 데 사용된다.

:: 연산자는 aliasName과 System.Console을 구분하는 데 사용한다.

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# Operator 키워드  (0) 2023.04.27
C# Method Parameter 키워드  (0) 2023.04.27
C# 멤버 명시 키워드 : this  (0) 2023.04.25
C# 상위 멤버 접근 키워드 : base  (0) 2023.04.25
C# 구조체 키워드 : struct  (0) 2023.04.25

this

인스턴스 특정

현재 인스턴스를 가리키는 기능을 한다.

주로 멤버 메서드나 생성자에서 인스턴스 변수를 참조할 때 사용한다.

 

class Person {
    private string name;
    private int age;

    public Person(string name, int age) {
        this.name = name;
        this.age = age;
    }

    public void PrintInfo() {
        Console.WriteLine("Name: " + this.name);
        Console.WriteLine("Age: " + this.age);
    }
}

이와 같은 경우 this 키워드는 동일한 이름의 name과 age를 멤버와 매개변수를 구분하는 데 사용된다.

PrintInfo 내에서는 this를 사용하지 않고 호출해도 되지만 일관성을 유지하기 위해 사용하는 등 개발자의 선호도에 따라 사용여부가 다르다.

 

 

생성자 호출

다른 생성자를 호출할 때 사용할 수 있다.

class Person {
    private string name;
    private int age;

    public Person(string name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(string name) : this(name, 0) {
    }

    public void PrintInfo() {
        Console.WriteLine("Name: " + this.name);
        Console.WriteLine("Age: " + this.age);
    }
}

위와 같이 두 개의 오버로딩된 생성자가 선언되었을 때 string name 매개변수만 받는 생성자의 경우 호출될 때 Person(string name, int age) 생성자를 호출하며 이때 age 값을 0으로 호출하는 동작이 수행된다.

 

인덱서

인덱서는 클래스나 구조체 등의 객체를 배열처럼 인덱싱할 수 있게 해주는 것으로 대괄호 안에 인덱스를 전달하여 객체의 멤버 변수나 속성 값을 가져오거나 설정할 수 있다.

 

public class MyList
{
    private string[] _data = new string[10];

    public string this[int index]
    {
        get => _data[index];
        set => _data[index] = value;
    }
}

class Program
{
    static void Main(string[] args)
    {
        MyList list = new MyList();
        list[0] = "Hello";
        list[1] = "World";
        Console.WriteLine(list[0]); // "Hello" 출력
        Console.WriteLine(list[1]); // "World" 출력
    }
}

this 키워드로 정의된 인덱서는 string 형식의 배열 _data 멤버 변수를 instance [indexer]와 같이 바로 접근하여 사용할 수 있다. 

 

인덱서를 사용하지 않을 경우에는 해당 멤버에 접근하기 위해서 별도의 public 프로퍼티나 메서드 또는 _data를 public으로 선언해야 한다.

 

 

 

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# Method Parameter 키워드  (0) 2023.04.27
C# Namespace 키워드  (0) 2023.04.27
C# 상위 멤버 접근 키워드 : base  (0) 2023.04.25
C# 구조체 키워드 : struct  (0) 2023.04.25
C# 문자열 키워드 : string  (0) 2023.04.25

base

상위 클래스로부터 파생된 클래스에서 사용할 수 있는 키워드로 상위 클래스의 멤버에 액세스 할 때 사용된다.

예를 들어 상위 클래스에서 정의된 멤버를 파생 클래스에서 다시 구현할 때 base 키워드를 사용하면 상위 클래스의 멤버에 접근할 수 있다.

 

public class Parent{
	virtual public void CallFunc(){
    	Console.WriteLine("Parent Call");
    }
}

public class Child : Parent{
	override public void CallFunc(){
    	base.CallFunc();
        Console.WriteLine("Child Call");
    }
}

Child child = new Child();
child.CallFunc();
// Parent Call, Child Call 모두 출력됨

 

728x90
반응형

'Program Language > C#' 카테고리의 다른 글

C# Namespace 키워드  (0) 2023.04.27
C# 멤버 명시 키워드 : this  (0) 2023.04.25
C# 구조체 키워드 : struct  (0) 2023.04.25
C# 문자열 키워드 : string  (0) 2023.04.25
C# 정수형 키워드 : int, long, short ...  (0) 2023.04.25

+ Recent posts