공부/C#

.Net 라이브러리

월러비 2025. 8. 26. 17:22

.NET 라이브러리

String과 텍스트 핸들링

문자(Char) 다루기

  • 문자(Char)는 단일 유니코드 문자를 나타내는 기본 구조체임. 16비트 크기를 가지며 System.Char 구조체에 대한 별칭임.
  • 기본적인 문자 리터럴 표현
char c = 'A';
char newLine = '\\n';  // 특수 문자

  • 주요 정적 메서드들
    • ToUpper(), ToLower(): 대소문자 변환
    • IsWhiteSpace(): 공백 문자 여부 확인
    • IsLetter(), IsDigit(): 문자, 숫자 여부 확인
Console.WriteLine(char.ToUpper('c'));  // 출력: C
Console.WriteLine(char.IsWhiteSpace('\\t'));  // 출력: True
Console.WriteLine(char.IsLetter('A'));  // 출력: True
Console.WriteLine(char.IsDigit('5'));  // 출력: True

문자열(String) 다루기

  • 문자열(String)은 일련의 문자들을 나타내는 참조 형식임. 불변(Immutable) 특성을 가짐.
  • 불변 : 인스턴스를 만들면 값변경이 안되는 속성이다.
    • 새로운 인스턴스를 참조하는 식으로 변경된다.
  • 문자열 : 클래스이기에 ‘참조 형식’이다.
  • 문자열 생성 방법
// 직접 할당
string s1 = "Hello";

// 반복 문자로 생성
string stars = new string('*', 10);  // **********

// 문자 배열로 생성
char[] ca = "Hello".ToCharArray();
string s2 = new string(ca);

  • null과 빈 문자열
string empty = "";  // 빈 문자열
string empty2 = string.Empty;  // 위와 동일
string nullString = null;  // null 문자열

Console.WriteLine(empty == "");  // True
Console.WriteLine(nullString == null);  // True
Console.WriteLine(nullString == "");  // False

  • 문자열 검색
string text = "quick brown fox";
// 문자열 시작, 포함 여부 확인
Console.WriteLine(text.StartsWith("quick"));  // True
Console.WriteLine(text.Contains("brown"));    // True

// 위치 찾기
Console.WriteLine(text.IndexOf("fox"));  // 12 / fox가 시작하는 인덱스 반환

문자열 조작

  • 문자열의 불변성으로 인해 모든 조작 메서드는 새로운 문자열을 반환함.
// 부분 문자열 추출
string numbers = "12345";
string left3 = numbers.Substring(0, 3);  // "123" / 시작 인덱스부터 몇개 추출
string mid3 = numbers.Substring(1, 3);   // "234"

// 삽입과 제거
string s1 = "helloworld".Insert(5, ", ");  // "hello, world" / 인덱스 뒤에 추가
string s2 = s1.Remove(5, 2);               // "helloworld" / 인덱스 뒤부터 몇개 제거

// 패딩
Console.WriteLine("12345".PadLeft(9, '*'));   // ****12345 / 총 길이, 추가할 문자
Console.WriteLine("12345".PadRight(9, '*'));  // 12345****

// 공백 삭제
Console.WriteLine (" abc \\t\\r\\n ".Trim().Length); // 3
Console.WriteLine("Trim() : '{0}'", " No Spaces ".Trim());
Console.WriteLine("TrimStart() : '{0}'", " No Spaces ".TrimStart());
Console.WriteLine("TrimEnd() : '{0}'", " No Spaces ".TrimEnd());

// Replace
Console.WriteLine ("to be done".Replace (" ", " | ") );  // to | be | done
Console.WriteLine ("to be done".Replace (" ", "")    );  // tobedone

// 기본 동작: 공백 기준으로 문자열 자르기
string[] words = "The quick brown fox".Split();
foreach (string word in words)
  Console.Write (word + "|");    // The|quick|brown|fox

  • Trim : 공백 문자 앞뒤 제거
    • \r , \t , \n 을 공백으로 판단한다.
    • 앞과 뒤의 공백을 제거하는것이니 중간 공백은 지우지 못한다.
  • Replace : 바꿀 문자, 대체할 문자

StringBuilder 사용

  • StringBuilder는 가변 문자열을 다루는 클래스임. 문자열 연결 작업이 많을 때 성능 향상을 위해 사용함.
StringBuilder sb = new StringBuilder();

// 문자열 추가
for (int i = 0; i < 10; i++)
{
    sb.Append(i).Append(",");
}
Console.WriteLine(sb.ToString());  // "0,1,2,3,4,5,6,7,8,9,"

// 줄 추가
StringBuilder sb2 = new StringBuilder();
sb2.AppendLine("First line");
sb2.AppendLine("Second line");
Console.WriteLine(sb2.ToString());

// 문자열 삽입과 제거
StringBuilder sb3 = new StringBuilder("Hello World");
sb3.Insert(5, " Beautiful");  // "Hello Beautiful World"
sb3.Remove(5, 10);           // "Hello World"

  • Append(문자) : 들어온 문자를 계속 연결하는 함수다.
    • string + 연산자의 역할이다.
    • string에서 + 연산자를 쓰지 말아야할 이유 : 호출할때마다 가비지 컬렉터가 계속 동작해야하기 때문이다.
    • 따라서, 문자열 연결이 있을 시 StringBuilder를 사용해라

문자열 포맷팅

  • 문자열 포맷팅(String Formatting)은 문자열에 데이터를 삽입하여 원하는 형식으로 표현하는 방법임
  • String.Format() 메서드를 사용하거나 복합 포맷 문자열을 활용함

String.Format 메서드

  • 기본 사용법:
    string name = "홍길동";
    int age = 20;
    double height = 175.5;
    
    // 여러 값을 하나의 문자열로 조합
    string info = string.Format(
        "이름: {0}\\n나이: {1}세\\n키: {2:F1}cm", //F1 : 소수점 1자리까지만 출력
        name, age, height);
    Console.WriteLine(info);
    
    // 동일한 값을 여러 번 사용
    string repeated = string.Format(
        "{0}님 안녕하세요! {0}님의 나이는 {1}세입니다.",
        name, age);
    Console.WriteLine(repeated);
    
    // 출력:
    // 이름: 홍길동
    // 나이: 20세
    // 키: 175.5cm
    // 홍길동님 안녕하세요! 홍길동님의 나이는 20세입니다.
    
    
  • string composite = "It's {0} degrees in {1} on this {2} morning"; //서식 문자열 string result = string.Format(composite, 35, "Perth", DateTime.Now.DayOfWeek); Console.WriteLine(result); // 출력: It's 35 degrees in Perth on this Friday morning
  • 보간 문자열(Interpolated string) 사용:
  • string result = $"It's hot this {DateTime.Now.DayOfWeek} morning"; Console.WriteLine(result); // 출력: It's hot this Friday morning

숫자 포맷팅

소수점 자릿수 제한

double price = 123.4567;
Console.WriteLine($"금액: {price:F2}"); // 금액: 123.46
Console.WriteLine($"금액: {price:N2}"); // 금액: 123.46 (천단위 구분기호 포함)
Console.WriteLine(string.Format("금액: {0:F4}", price)); // 금액: 123.4567

백분율 표시

double ratio = 0.175;
Console.WriteLine($"비율: {ratio:P0}"); // 비율: 18%
Console.WriteLine($"비율: {ratio:P1}"); // 비율: 17.5%

통화 표시

decimal money = 12345.6789m;
Console.WriteLine($"통화: {money:C}"); // 통화: ₩12,346
Console.WriteLine($"통화: {money:C3}"); // 통화: ₩12,345.679

16진수 표시

int value = 255;
Console.WriteLine($"16진수(소문자): {value:x}"); // 16진수(소문자): ff
Console.WriteLine($"16진수(대문자): {value:X}"); // 16진수(대문자): FF
Console.WriteLine($"16진수(4자리): {value:X4}"); // 16진수(4자리): 00FF

포맷 항목 구조와 정렬

최소 너비 지정과 정렬

// 왼쪽 정렬(-), 오른쪽 정렬(+)
string header = string.Format("{0,-10} {1,10}", "이름", "점수");
string row1 = string.Format("{0,-10} {1,10:N0}", "김철수", 95);
string row2 = string.Format("{0,-10} {1,10:N0}", "이영희", 100);

Console.WriteLine(header);
Console.WriteLine(row1);
Console.WriteLine(row2);

// 출력:
// 이름              점수
// 김철수            95
// 이영희           100

날짜와 시간 포맷팅

DateTime now = DateTime.Now;
/[]p'\\
Console.WriteLine($"기본: {now}"); // 2024-12-18 14:30:45
Console.WriteLine($"short date: {now:d}"); // 2024-12-18
Console.WriteLine($"long date: {now:D}"); // 2024년 12월 18일 수요일
Console.WriteLine($"short time: {now:t}"); // 14:30
Console.WriteLine($"custom: {now:yyyy-MM-dd HH:mm}"); // 2024-12-18 14:30

숫자 자릿수 맞추기

for (int i = 1; i <= 12; i++)
{
    Console.WriteLine($"Chapter {i:D2}"); // 01부터 12까지 두 자리로 표시
}

// 출력:
// Chapter 01
// Chapter 02
// ...
// Chapter 12

날짜와 시간 다루기

TimeSpan 활용

TimeSpan은 시간 간격을 나타내는 구조체임. 게임에서 경과 시간, 쿨타임 등을 구현할 때 유용함.

  • 생성 방법
// 생성자 사용
TimeSpan t1 = new TimeSpan(2, 30, 0);  // 2시간 30분

// 정적 메서드 사용
TimeSpan t2 = TimeSpan.FromHours(2.5);    // 2.5시간
TimeSpan t3 = TimeSpan.FromMinutes(150);  // 150분

  • 시간 계산
TimeSpan cooldown = TimeSpan.FromSeconds(30);
TimeSpan elapsed = TimeSpan.FromSeconds(20);

// 남은 시간 계산
TimeSpan remaining = cooldown - elapsed;  // 10초

// 시간 요소 접근
Console.WriteLine(remaining.TotalSeconds);  // 10
Console.WriteLine(remaining.Seconds);       // 10

DateTime 활용

DateTime은 특정 날짜와 시간을 나타내는 구조체임. 게임의 저장/로드 시간, 이벤트 시간 등을 다룰 때 사용함.

  • DateTime 포맷팅
DateTime eventTime = new DateTime(2024, 12, 25);

// 기본 포맷
Console.WriteLine(eventTime.ToString());  // 2024-12-25 00:00:00

// 사용자 지정 포맷
Console.WriteLine(eventTime.ToString("yyyy-MM-dd"));  // 2024-12-25
Console.WriteLine(eventTime.ToString("MM/dd/yyyy HH:mm"));  // 12/25/2024 00:00

// 게임에서 자주 사용하는 포맷
Console.WriteLine(eventTime.ToString("M"));  // 12월 25일
Console.WriteLine(eventTime.ToString("t"));  // 00:00

  • 기본 사용
// 특정 날짜/시간 생성
DateTime gameStartTime = new DateTime(2024, 1, 1, 12, 0, 0);

// 현재 시간
DateTime now = DateTime.Now;

// 날짜 연산
DateTime end = now.AddHours(2);  // 2시간 후
TimeSpan duration = end - now;   // 기간 계산

  • 게임에서의 활용: 쿨타임 시스템
public class Skill
{
    private TimeSpan cooldown = TimeSpan.FromSeconds(30);
    private DateTime? lastUsedTime = null;

    public bool CanUse()
    {
        if (lastUsedTime == null) return true;

        TimeSpan elapsed = DateTime.Now - lastUsedTime.Value;
        return elapsed >= cooldown;
    }

    public void Use()
    {
        if (CanUse())
        {
            Console.WriteLine("스킬 사용!");
            lastUsedTime = DateTime.Now;
        }
        else
        {
            TimeSpan remaining = cooldown -
                (DateTime.Now - lastUsedTime.Value);
            Console.WriteLine($"아직 {remaining.TotalSeconds:F1}초 남음");
        }
    }
}

수치 처리와 수학

Math 클래스 활용

  • Math 클래스는 기본적인 수학 연산을 제공하는 정적 클래스임.
    • 유니티는 Mathf 클래스가 따로 있다
// 반올림, 올림, 내림
double d = 3.7;
Console.WriteLine(Math.Round(d));    // 4 //반올림
Console.WriteLine(Math.Floor(d));    // 3 / 내림
Console.WriteLine(Math.Ceiling(d));  // 4 / 올림

// 최대값, 최소값, 절대값
Console.WriteLine(Math.Max(10, 20));  // 20
Console.WriteLine(Math.Min(10, 20));  // 10
Console.WriteLine(Math.Abs(-50));     // 50

// 제곱, 제곱근, 삼각함수
Console.WriteLine(Math.Pow(2, 3));        // 8
Console.WriteLine(Math.Sqrt(16));         // 4
Console.WriteLine(Math.Sin(Math.PI / 2)); // 1 / 라디안 단위다.

BigInteger 활용

  • BigInteger는 임의 크기의 정수를 다룰 수 있는 구조체임. 매우 큰 수치가 필요한 게임에서 유용함.
    • 방치형 게임에서 사용된다. - 수치 자릿수가 크기 때문이다.
using System.Numerics;

// BigInteger 생성과 연산
BigInteger exp = BigInteger.Parse("999999999999999999999999999");
BigInteger gold = new BigInteger(1000000);
gold *= 1000000;  // 매우 큰 수로 증가

// 게임 예제: 클리커 게임
public class ClickerGame
{
    private BigInteger score = 0;
    private BigInteger clickPower = 1;

    public void Click()
    {
        score += clickPower;
        Console.WriteLine($"현재 점수: {score:N0}");
    }

    public void UpgradeClickPower()
    {
        clickPower *= 2;
        Console.WriteLine($"클릭 파워 증가! 현재: {clickPower:N0}");
    }
}

암호학적으로 안전한 난수 생성

보안이 중요한 부분(예: 확률형 아이템)에서는 암호학적으로 안전한 난수를 사용할 수 있음.

using System.Security.Cryptography;

// 안전한 난수 생성
byte[] bytes = new byte[32];
RandomNumberGenerator.Fill(bytes);

// 1-100 사이의 안전한 난수 얻기
int secureRandom = (bytes[0] % 100) + 1;
Console.WriteLine($"보안 난수: {secureRandom}");

Random 클래스 활용

  • Random 클래스는 의사 난수를 생성하는데 사용됨.
  • 가산 함수 : 시드 같은 특정 숫자 또는 불규칙적인 수를 반환하는 함수를 의미한다.
Random rand = new Random();

// 정수 범위의 난수
Console.WriteLine(rand.Next(1, 101));  // 1-100 사이

// 실수 범위의 난수
Console.WriteLine(rand.NextDouble());   // 0.0-1.0 사이

// 게임 예제: 아이템 드롭 시스템
public class ItemDropSystem
{
    private Random rand = new Random();

    public bool TryDropItem(double dropRate)
    {
        return rand.NextDouble() < dropRate;
    }

    public int GetRandomDamage(int minDamage, int maxDamage)
    {
        return rand.Next(minDamage, maxDamage + 1);
    }
}

객체 비교와 정렬

객체 비교의 기본 개념

  • 값 형식(struct)은 기본적으로 값 비교를 수행함
  • 참조 형식(class)은 기본적으로 참조 비교를 수행함
    • 2개의 참조형식 비교가 같은 인스턴스를 참조하고 있는지 확인하는 것이다.
    • 하지만, string 클래스틑 참조 형식이지만, == 같다 비교는 ‘값 형식’으로 비교를 진행한다.
// 값 비교 예시
int x = 5, y = 5;
Console.WriteLine(x == y);  // True

// 참조 비교 예시
class Player { public int Id; }
Player p1 = new Player { Id = 5 };
Player p2 = new Player { Id = 5 };
Console.WriteLine(p1 == p2);  // False

Equals와 GetHashCode 구현

  • 객체의 동등성 비교를 위해서는 Equals와 GetHashCode를 함께 구현해야 함.
public class GameItem
{
    public string Name { get; set; }
    public int Level { get; set; }
		
		//값 비교를 위한 Equals 재구현
    public override bool Equals(object obj)
    {
        if (obj == null || GetType() != obj.GetType())
            return false;

        GameItem other = (GameItem)obj;
        return Name == other.Name && Level == other.Level;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Name, Level);
    }
}

  • Equals : bool형 리턴
  • GetHashCode : 입력값이 같다면 동일한 결과를 반환한다.

IComparable 구현

  • 객체의 정렬 순서를 정의하려면 IComparable 인터페이스를 구현함.
public class Score : IComparable<Score>
{
    public string PlayerName { get; set; }
    public int Points { get; set; }
		
		//정렬 기준이 같은지 확인하는 함수기에, 결과가 다를 수 있다.
    public int CompareTo(Score other)
    {
        if (other == null) return 1;
        return other.Points.CompareTo(Points);  // 내림차순 정렬
    }
}

// 사용 예시
var scores = new List<Score>
{
    new Score { PlayerName = "Alice", Points = 100 },
    new Score { PlayerName = "Bob", Points = 150 },
    new Score { PlayerName = "Charlie", Points = 120 }
};

scores.Sort();
// 결과: Bob(150) > Charlie(120) > Alice(100)

게임 예제: 아이템 인벤토리 시스템

아이템 동등성 비교와 해시 기능을 활용한 실전적인 인벤토리 시스템 예제.

public class InventoryItem : IEquatable<InventoryItem>
{
    public string Id { get; set; }
    public string Name { get; set; }
    public int Count { get; set; }

    public override bool Equals(object obj)
    {
        return Equals(obj as InventoryItem);
    }

    public bool Equals(InventoryItem other)
    {
        if (other == null) return false;
        return Id == other.Id;  // ID만으로 동일성 판단
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

public class Inventory
{
    private HashSet<InventoryItem> items = new HashSet<InventoryItem>();

    public bool AddItem(InventoryItem item)
    {
        return items.Add(item);  // 중복 아이템은 추가되지 않음
    }

    public void DisplayItems()
    {
        foreach (var item in items)
        {
            Console.WriteLine($"{item.Name} x{item.Count}");
        }
    }
}

// 사용 예시
var inventory = new Inventory();
inventory.AddItem(new InventoryItem { Id = "SWORD1", Name = "강철검", Count = 1 });
inventory.AddItem(new InventoryItem { Id = "POTION1", Name = "체력 포션", Count = 5 });
// 중복 아이템 추가 시도
inventory.AddItem(new InventoryItem { Id = "SWORD1", Name = "강철검", Count = 1 });

inventory.DisplayItems();
// 출력:
// 강철검 x1
// 체력 포션 x5