다이아몬드 문제(Diamond Problem) :
다중 상속을 허용하면 한 클래스가 두 개 이상의 부모 클래스로부터 동일한 멤버를 상속받을수 있다. 이 경우, 같은 이름의 멤버를 가지고 있을 때 어떤 부모 클래스의 멤버를 사용해야 하는지 모호해진다. 이런 모호성을 해결하기 위한 규칙이 필요하게 되는데, 이로 인해 코드가 복잡해지고 가독성이 저하될 수 있다.
설계의 복잡성 증가 :
다중 상속을 허용하면 클래스 간의 관계가 복잡해진다. 클래스가 다중 상속을 받을 경우, 어떤 클래스로부터 어떤 멤버를 상속받을지 결정해야 한다. 이로 인해 클라스 간의 상속 관계를 파악하기 어려워지고 코드의 유지 보수성이 저하될 수 있다.
이름 충돌과 충돌 해결의 어려운 :
다중 상속을 허용하면 여러 부모 클래스로부터 상속받은 멤버들의 이름이 충돌할 수 있다. 이러한 충돌을 해결하기 위해 충돌하는 멤버를 재정의해야 하거나 명시적으로 부모 클래스를 지정해야 할 수 있다. 이는 코드의 복잡성을 증가시키고 오류 발생 가능성을 높인다.
설계의 일관성과 단순성 유지 :
C#은 단일 상속을 통해 설계의 일관성과 단순성을 유지하고자 한다. 단일 상속을 통해 클래스 간의 관계를 명확하게 만들고 코드의 가독성과 이해도를 높일 수 있다. 또한 인터페이스를 사용하여 다중 상속이 필요한 경우에도 유사한 기능을 구현할 수 있다.
코드의 재사용성 :
인터페이스를 사용하면 다른 클래스에서 해당 인터페이스를 구현하여 동일한 기능을 공유할 수 있다. 인터페이스를 통해 다양한 클래스가 동일한 동작을 수행할 수 있으므로 코드의 재사용성이 향상된다.
다중 상속 제공 :
C#에서는 클래스는 단일 상속만 지원하지만, 인터페이스는 다중 상속을 지원한다. 클래스가 여러 인터페이스를 구현함으로써 여러 개의 기능을 조합할 수 있다. 다중 상속을 통해 클래스는 더 다양한 동작을 수행할 수 있다.
유연한 설계 :
인터페이스를 사용하면 클래스와 인터페이스 간에 느슨한 결합을 형성할 수 있다. 클래스는 인터페이스를 구현하기만 하면 되므로, 클래스의 내부 구현에 대한 변경 없이 인터페이스의 동작을 변경하거나 새로운 인터페이스를 추가할 수 있다. 이는 유연하고 확장 가능한 소프트웨어 설계를 가능하게 한다.
인터페이스란 클래스가 구현해야 하는 멤버들을 정의하는 것이다.
인터페이스는 클래스의 일종이 아니며, 클래스에 대한 제약 조건을 명시하는 것이다.
클래스가 인터페이스를 구현할 경우, 모든 인터페이스 멤버를 구현해야 한다.
인터페이스는 다중 상속을 지원한다.
interface IMyInterface
{
void Method1();
int Method2(string str);
}
class MyClass : IMyInterface
{
public void Method1()
{
// 구현
}
public int Method2(string str)
{
// 구현
return 0;
}
}
- 인터페이스 구현해보기
public interface IMovable
{
void Move(int x, int y); // 이동 메서드 선언
}
public class Player : IMovable
{
public void Move(int x, int y)
{
// 플레이어의 이동 구현
}
}
public class Enemy : IMovable
{
public void Move(int x, int y)
{
// 적의 이동 구현
}
}
IMovable movableObject1 = new Player();
IMovable movableObject2 = new Enemy();
movableObject1.Move(5, 0); // 플레이어 이동
movableObject2.Move(1, 9); // 적 이동
- 아이템 사용 구현 예제
// 아이템을 사용할 수 있는 인터페이스
public interface IUsable
{
void Use();
}
// 아이템 클래스
public class Item : IUsable
{
public string Name { get; set; }
public void Use()
{
Console.WriteLine("아이템 {0}을 사용했습니다.", Name);
}
}
// 플레이어 클래스
public class Player
{
public void UseItem(IUsable item)
{
item.Use();
}
}
// 게임 실행
static void Main()
{
Player player = new Player();
Item item = new Item { Name = "Health Potion" };
player.UseItem(item);
}
- 다중 상속 구현 예제
// 인터페이스 1
public interface IItemPickable
{
void PickUp();
}
// 인터페이스 2
public interface IDroppable
{
void Drop();
}
// 아이템 클래스
public class Item : IItemPickable, IDroppable
{
public string Name { get; set; }
public void PickUp()
{
Console.WriteLine("아이템 {0}을 주웠습니다.", Name);
}
public void Drop()
{
Console.WriteLine("아이템 {0}을 버렸습니다.", Name);
}
}
// 플레이어 클래스
public class Player
{
public void InteractWithItem(IItemPickable item)
{
item.PickUp();
}
public void DropItem(IDroppable item)
{
item.Drop();
}
}
// 게임 실행
static void Main()
{
Player player = new Player();
Item item = new Item { Name = "Sword" };
// 아이템 주울 수 있음
player.InteractWithItem(item);
// 아이템 버릴 수 있음
player.DropItem(item);
}
인터페이스의 특징과 장단점 :
인터페이스는 추상적인 동작만 정의하고, 구현을 갖지 않는다.
다중 상속이 가능하며, 여러 클래스가 동일한 인터페이스를 구현할 수 있다.
클래스들 간의 결합도를 낮추고, 유연한 상호작용을 가능하게 한다.
코드의 재사용성과 확장성을 향상시킨다.
단점으로는 인터페이스를 구현하는 클래스가 모든 동작을 구현해야 한다는 의무를 가지기 때문에 작업량이 증가할 수 있다.
추상 클래스의 특징과 장단점 :
추상 클래스는 일부 동작의 구현을 가지며, 추상 메서드를 포함할 수 있다.
단일 상속만 가능하며, 다른 클래스와 함께 상속 계층 구조를 형성할 수 있다.
공통된 동작을 추상화하여 코드의 중복을 방지하고, 확장성을 제공한다.
구현된 동작을 가지고 있기 때문에, 하위 클래스에서 재정의하지 않아도 될 경우 유용하다.
단점으로는 다중 상속이 불가능하고, 상속을 통해 밀접하게 결합된 클래스들을 형성하므로 유연성이 제한될 수 있다.
가독성 : 열거형을 사용하면 일련의 연관된 상수들을 명명할 수 있다. 이를 통해 코드의 가독성이 향상되고, 상수를 사용할 때 실수로 잘못된 값을 할당하는 것을 방지할 수 있다.
자기 문서화(Self-documenting) : 열거형은 의미 있는 이름을 사용하여 상수를 명명할 수 있다. 이를 통해 코드의 가독성이 향상되며, 상수의 의미를 명확하게 설명할 수 있다.
스위치 문과의 호환성 : 열거형은 스위치 문과 함께 사용될 때 유용하다. 열거형을 사용하면 스위치 문에서 다양한 상수 값에 대한 분기를 쉽게 작성할 수 있다.
열거형은 서로 관련된 상수들의 집합을 정의할 때 사용된다.
열거형의 각 상수는 정수 값으로 지정된다.
enum MyEnum
{
Value1,
Value2,
Value3
}
MyEnum myEnum = MyEnum.Value1;
enum MyEnum
{
Value1 = 10,
Value2,
Value3 = 20
}
int intValue = (int)MyEnum.Value1; // 열거형 값을 정수로 변환
MyEnum enumValue = (MyEnum)intValue; // 정수를 열거형으로 변환
switch(enumValue)
{
case MyEnum.Value1:
// Value1에 대한 처리
break;
case MyEnum.Value2:
// Value2에 대한 처리
break;
case MyEnum.Value3:
// Value3에 대한 처리
break;
default:
// 기본 처리
break;
}
enum DaysOfWeek
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
class Program
{
static void Main(string[] args)
{
DaysOfWeek day = DaysOfWeek.Monday;
Console.WriteLine("Today is " + day);
}
}
// 월 열거형
public enum Month
{
January = 1,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December
}
// 처리하는 함수
static void ProcessMonth(int month)
{
if (month >= (int)Month.January && month <= (int)Month.December)
{
Month selectedMonth = (Month)month;
Console.WriteLine("선택한 월은 {0}입니다.", selectedMonth);
// 월에 따른 처리 로직 추가
}
else
{
Console.WriteLine("올바른 월을 입력해주세요.");
}
}
// 실행 예제
static void Main()
{
int userInput = 7; // 사용자 입력 예시
ProcessMonth(userInput);
}
-게임 사용 사례
// 게임 상태
enum GameState
{
MainMenu,
Playing,
Paused,
GameOver
}
// 방향
enum Direction
{
Up,
Down,
Left,
Right
}
// 아이템 등급
enum ItemRarity
{
Common,
Uncommon,
Rare,
Epic
}
예외는 프로그램 실행 중에 발생하는 예기치 않은 상황을 의미한다.
예외는 프로그램의 정상적인 흐름을 방해하고 오류를 야기할 수 있다.
예외 처리는 예외 상황에 대비하여 프로그램을 안정적으로 유지하는 데 도움을 준다.
예외 처리를 통해 오류 상황을 적절히 처리하고, 프로그램의 실행을 계속할 수 있다.
예외 처리는 프로그램의 안정성을 높이고 디버깅을 용이하게 한다.
C#에서는 try-catch 블록을 사용하여 예외 처리를 수행한다.
try 블록 내에서 예외가 발생할 수 있는 코드를 작성하고, catch 블록에서 예외를 처리한다.
try
{
// 예외가 발생할 수 있는 코드
}
catch (ExceptionType1 ex)
{
// ExceptionType1에 해당하는 예외 처리
}
catch (ExceptionType2 ex)
{
// ExceptionType2에 해당하는 예외 처리
}
finally
{
// 예외 발생 여부와 상관없이 항상 실행되는 코드
}
catch 블록은 위에서부터 순서대로 실행되며, 예외 타입에 해당하는 첫 번째 catch블록이 실행된다.
예외 타입은 상속 관계에 있는 경우 상위 예외 타입의 catch블록이 먼저 실행된다.
여러 개의 catch블록을 사용하여 다향한 예외 타입을 처리할 수 있다.
다중 catch블록을 사용하면 각각의 예외 타입에 따라 다른 예외 처리 코드를 작성할 수 있다.
catch 블록에서는 예외 객체를 사용하여 예외에 대한 정보를 액세스할 수 있다.
예외 객체를 사용하여 예외의 타입, 메세지 등을 확인하고 처리할 수 있다.
finally블록은 예외 발생 여부와 상관없이 항상 실행되는 코드 블록이다.
finally블록은 예외 처리의 마지막 단계로, 예외 발생 시 정리 작업이나 리소스 해제 등의 코드를 포함할 수 있다.
finally블록은 try-catch블록 뒤에 작성되며, 생략할 수도 있다.
예외가 발생한 경우 : 예외가 발생하면 예외 처리 과정을 거친 후 finally블록이 실행된다.
예외가 발생하지 않은 경우 : 예외가 발생하지 않아도 finally블록은 정상적으로 실행된다.
사용자는 필요에 따라 자신만의 예외 클래스를 작성할 수 있다.
사용자 정의 예외 클래스는 Exception클래스를 상속받아 작성하며, 추가적인 기능이나 정보를 제공할 수 있다.
사용자 정의 예외가 발생한 경우, try-catch블록에서 해당 예외를 처리할 수 있다.
catch블록에서 사용자 정의 예외 타입을 명시하여 예외를 처리하고, 예외에 대한 적절한 처리 로직을 작성할 수 있다.
try
{
int result = 10 / 0; // ArithmeticException 발생
Console.WriteLine("결과: " + result);
}
catch (DivideByZeroException ex)
{
Console.WriteLine("0으로 나눌 수 없습니다.");
}
catch (Exception ex)
{
Console.WriteLine("예외가 발생했습니다: " + ex.Message);
}
finally
{
Console.WriteLine("finally 블록이 실행되었습니다.");
}
public class NegativeNumberException : Exception
{
public NegativeNumberException(string message) : base(message)
{
}
}
try
{
int number = -10;
if (number < 0)
{
throw new NegativeNumberException("음수는 처리할 수 없습니다.");
}
}
catch (NegativeNumberException ex)
{
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("예외가 발생했습니다: " + ex.Message);
}
// 플레이어 이동
try
{
// 플레이어 이동 코드
if (IsPlayerCollidingWithWall())
{
throw new CollisionException("플레이어가 벽에 충돌했습니다!");
}
}
catch (CollisionException ex)
{
// 충돌 예외 처리
Debug.Log(ex.Message);
// 예외에 대한 추가 처리
}
// 리소스 로딩
try
{
// 리소스 로딩 코드
LoadResource("image.png");
}
catch (ResourceNotFoundException ex)
{
// 리소스가 없는 경우 예외 처리
Debug.Log(ex.Message);
// 예외에 대한 추가 처리
}
catch (ResourceLoadException ex)
{
// 리소스 로딩 중 오류가 발생한 경우 예외 처리
Debug.Log(ex.Message);
// 예외에 대한 추가 처리
}
// 게임 상태 전이
try
{
// 상태 전이 코드
if (currentGameState != GameState.Playing)
{
throw new InvalidStateException("게임이 실행 중이 아닙니다!");
}
// 게임 상태 전이 실행
}
catch (InvalidStateException ex)
{
// 상태 예외 처리
Debug.Log(ex.Message);
// 예외에 대한 추가 처리
}
C#에서 변수가 데이터를 저장하는 방식을 나타낸다.
값형은 변수에 값을 직접 저장한다.
변수가 실제 데이터를 보유하고 있으며, 해당 변수를 다른 변수에 할당하거나 전달할 때는 값이 복사된다.
값형 변수의 수정은 해당 변수의 값만 변경하므로 다른 변수에 영향을 주지 않는다.
int, float, double, bool등의 기본 데이터 타입들이 값형에 해당한다.
struct MyStruct
{
public int Value;
}
MyStruct struct1 = new MyStruct();
struct1.Value = 10;
MyStruct struct2 = struct1; // struct2는 struct1의 값 복사
struct2.Value = 20;
Console.WriteLine(struct1.Value); // 출력 결과: 10
참조형은 변수가 데이터에 대한 참조(메모리 주소)를 저장한다.
변수가 실제 데이터를 가리키는 참조를 갖고 있으며, 해당 변수를 다른 변수에 할당하거나 전달할 때는 참조가 복사된다.
참조형 변수의 수정은 동일한 데이터를 가리키고 있는 다른 변수에 영향을 줄 수 있다.
클래스, 배열, 인터페이스 등이 참조형에 해당한다.
class MyClass
{
public int Value;
}
MyClass obj1 = new MyClass();
obj1.Value = 10;
MyClass obj2 = obj1; // obj2는 obj1과 동일한 객체를 참조
obj2.Value = 20;
Console.WriteLine(obj1.Value); // 출력 결과: 20
값형과 참조형 사이의 변환을 의미
박싱은 값형을 참조형으로 변환하는 과정을 말한다.
값형 변수의 값을 메모리의 힙 영역에 할당된 객체로 래핑합니다.
박싱을 통해 값형이 참조형의 특징을 갖게 되며, 참조형 변수로 다뤄질 수 있다.
박싱된 값형은 참조로 전달되므로 메모리 오버헤드가 발생할 수 있다.
언박싱은 박싱된 객체를 다시 값형으로 변환하는 과정을 말한다.
박싱된 객체에서 값을 추출하여 값형 변수에 할당한다.
언박싱은 명시적으로 타입 캐스팅을 해야 하며, 런타임에서 타입 검사가 이루어진다.
잘못된 형식으로 언박싱하면 런타임 에러가 발생할 수 있다.
박싱과 언박싱은 값형과 참조형 사이의 변환 작업이므로 성능에 영향을 미칠 수 있다.
반복적인 박싱과 언박싱은 성능 저하를 초래할 수 있으므로 주의해야 한다.
박싱된 객체는 힙 영역에 할당되므로 가비지 컬렉션의 대상이 될 수 있다. 따라서 메모리 관리에 유의해야 한다.
박싱된 객체와 원래의 값형은 서로 독립적이므로 값을 수정하더라도 상호간에 영향을 주지 않는다.
박싱과 언박싱 :
using System;
class Program
{
static void Main()
{
// 값형
int x = 10;
int y = x;
y = 20;
Console.WriteLine("x: " + x); // 출력 결과: 10
Console.WriteLine("y: " + y); // 출력 결과: 20
// 참조형
int[] arr1 = new int[] { 1, 2, 3 };
int[] arr2 = arr1;
arr2[0] = 4;
Console.WriteLine("arr1[0]: " + arr1[0]); // 출력 결과: 4
Console.WriteLine("arr2[0]: " + arr2[0]); // 출력 결과: 4
// 박싱과 언박싱
int num1 = 10;
object obj = num1; // 박싱
int num2 = (int)obj; // 언박싱
Console.WriteLine("num1: " + num1); // 출력 결과: 10
Console.WriteLine("num2: " + num2); // 출력 결과: 10
}
}
리스트 활용 예제 :
List<object> myList = new List<object>();
// 박싱: 값 형식을 참조 형식으로 변환하여 리스트에 추가
int intValue = 10;
myList.Add(intValue); // int를 object로 박싱하여 추가
float floatValue = 3.14f;
myList.Add(floatValue); // float를 object로 박싱하여 추가
// 언박싱: 참조 형식을 값 형식으로 변환하여 사용
int value1 = (int)myList[0]; // object를 int로 언박싱
float value2 = (float)myList[1]; // object를 float로 언박싱
예외 처리를 할 때는 가능한 구체적인 예외 클래스를 사용하면 좋다. 이렇게 하면 코드가 더욱 안정적이고 예외 상황에 대한 처리가 더욱 정확해진다.
값형과 참조형, 박싱과 언박싱의 개념을 확실히 이해해야 한다. 이는 C#에서 개발을 할 때 매우 중요한 개념 중 하나이다.
델리게이트(delegate)는 메서드를 참조하는 타입이다.
다른 프로그래밍 언어에서는 함수 포인터라는 용어를 사용하기도 한다.
델리게이트를 이용하면 메서드를 매개변수로 전달하거나 변수에 할당할 수 있다.
delegate int Calculate(int x, int y);
static int Add(int x, int y)
{
return x + y;
}
class Program
{
static void Main()
{
// 메서드 등록
Calculate calc = Add;
// 델리게이트 사용
int result = calc(3, 5);
Console.WriteLine("결과: " + result);
}
}
delegate void MyDelegate(string message);
static void Method1(string message)
{
Console.WriteLine("Method1: " + message);
}
static void Method2(string message)
{
Console.WriteLine("Method2: " + message);
}
class Program
{
static void Main()
{
// 델리게이트 인스턴스 생성 및 메서드 등록
MyDelegate myDelegate = Method1;
myDelegate += Method2;
// 델리게이트 호출
myDelegate("Hello!");
Console.ReadKey();
}
}
공격 콜백 받기 :
// 델리게이트 선언
public delegate void EnemyAttackHandler(float damage);
// 적 클래스
public class Enemy
{
// 공격 이벤트
public event EnemyAttackHandler OnAttack;
// 적의 공격 메서드
public void Attack(float damage)
{
// 이벤트 호출
OnAttack?.Invoke(damage);
// null 조건부 연산자
// null 참조가 아닌 경우에만 멤버에 접근하거나 메서드를 호출
}
}
// 플레이어 클래스
public class Player
{
// 플레이어가 받은 데미지 처리 메서드
public void HandleDamage(float damage)
{
// 플레이어의 체력 감소 등의 처리 로직
Console.WriteLine("플레이어가 {0}의 데미지를 입었습니다.", damage);
}
}
// 게임 실행
static void Main()
{
// 적 객체 생성
Enemy enemy = new Enemy();
// 플레이어 객체 생성
Player player = new Player();
// 플레이어의 데미지 처리 메서드를 적의 공격 이벤트에 추가
enemy.OnAttack += player.HandleDamage;
// 적의 공격
enemy.Attack(10.0f);
}
람다(lambda)는 익명 메서드를 만드는 방법이다.
람다를 사용하면 메서드의 이름 없이 메서드를 만들 수 있다.
람다는 델리게이트를 사용하여 변수에 할당하거나, 메서드의 매개변수로 전달할 수 있다.
(parameter_list) => expression
Calculate calc = (x, y) =>
{
return x + y;
};
Calculate calc = (x, y) => x + y;
using System;
// 델리게이트 선언
delegate void MyDelegate(string message);
class Program
{
static void Main()
{
// 델리게이트 인스턴스 생성 및 람다식 할당
MyDelegate myDelegate = (message) =>
{
Console.WriteLine("람다식을 통해 전달된 메시지: " + message);
};
// 델리게이트 호출
myDelegate("안녕하세요!");
Console.ReadKey();
}
}
// 델리게이트 선언
public delegate void GameEvent();
// 이벤트 매니저 클래스
public class EventManager
{
// 게임 시작 이벤트
public event GameEvent OnGameStart;
// 게임 종료 이벤트
public event GameEvent OnGameEnd;
// 게임 실행
public void RunGame()
{
// 게임 시작 이벤트 호출
OnGameStart?.Invoke();
// 게임 실행 로직
// 게임 종료 이벤트 호출
OnGameEnd?.Invoke();
}
}
// 게임 메시지 클래스
public class GameMessage
{
public void ShowMessage(string message)
{
Console.WriteLine(message);
}
}
// 게임 실행
static void Main()
{
// 이벤트 매니저 객체 생성
EventManager eventManager = new EventManager();
// 게임 메시지 객체 생성
GameMessage gameMessage = new GameMessage();
// 게임 시작 이벤트에 람다 식으로 메시지 출력 동작 등록
eventManager.OnGameStart += () => gameMessage.ShowMessage("게임이 시작됩니다.");
// 게임 종료 이벤트에 람다 식으로 메시지 출력 동작 등록
eventManager.OnGameEnd += () => gameMessage.ShowMessage("게임이 종료됩니다.");
// 게임 실행
eventManager.RunGame();
}
Func과 Action은 델리게이트를 대체하는 미리 정의된 제네릭 형식이다.
Func는 값을 반환하는 메서드를 나타내는 델리게이트이다. 마지막 제네릭 형식 매개변수는 반환 타입을 나타낸다. 예를 들어, Funce<int, string>
는 int
를 입력으로 받아 string
을 반환하는 메서드를 나타낸다.
Action은 값을 반환하지 않는 메서드를 나타내는 델리게이트이다. Action은 매개변수를 받아들이지만, 반환 타입이 없다. 예를 들어, Action<int, string>
은 int
와 string
을 입력으로 받고, 아무런 값을 반환하지 않는 메서드를 나타낸다.
Func및 Action은 제네릭 형식으로 미리 정의되어 있어 매개변수와 반환 타입을 간결하게 표현할 수 있다.
// Func를 사용하여 두 개의 정수를 더하는 메서드
int Add(int x, int y)
{
return x + y;
}
// Func를 이용한 메서드 호출
Func<int, int, int> addFunc = Add;
int result = addFunc(3, 5);
Console.WriteLine("결과: " + result);
// Action을 사용하여 문자열을 출력하는 메서드
void PrintMessage(string message)
{
Console.WriteLine(message);
}
// Action을 이용한 메서드 호출
Action<string> printAction = PrintMessage;
printAction("Hello, World!");
// 게임 캐릭터 클래스
class GameCharacter
{
private Action<float> healthChangedCallback;
private float health;
public float Health
{
get { return health; }
set
{
health = value;
healthChangedCallback?.Invoke(health);
}
}
public void SetHealthChangedCallback(Action<float> callback)
{
healthChangedCallback = callback;
}
}
// 게임 캐릭터 생성 및 상태 변경 감지
GameCharacter character = new GameCharacter();
character.SetHealthChangedCallback(health =>
{
if (health <= 0)
{
Console.WriteLine("캐릭터 사망!");
}
});
// 캐릭터의 체력 변경
character.Health = 0;
.NET플레임워크에서 제공되는 쿼리 언어 확장
데이터 소스(예 : 컬렉션, 데이터베이스, XML문서 등)에서 데이터를 쿼리하고 조작하는데 사용된다.
데이터베이스 쿼리와 유사한 방식으로 데이터를 필터링, 정렬, 그룹화, 조인 등 다양한 작업을 수행할 수 있다.
LINQ는 객체, 데이터베이스, XML문서 등 다양한 데이터 소스를 지원한다.
var result = from 변수 in 데이터소스
[where 조건식]
[orderby 정렬식 [, 정렬식...]]
[select 식];
var
키워드는 결과 값의 자료형을 자동으로 추론한다.
from
절에서는 데이터 소스를 지정한다.
where
절은 선택적으로 사용하며, 조건식을 지정하여 데이터를 필터링한다.
orderby
절은 선택적으로 사용하며, 정렬 방식을 지정한다.
select
절은 선택적으로 사용하며, 조회할 데이터를 지정한다.
// 데이터 소스 정의 (컬렉션)
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// 쿼리 작성 (선언적인 구문)
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
// 쿼리 실행 및 결과 처리
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
null은 "아무것도 없음"을 의미한다.
참조형 변수가 어떠한 객체를 참조하지 않을 때 사용된다.
Nullable은 C#에서 null값을 가질수 있는 값형에 대한 특별한 형식이다.
기본적으로 값형은 null을 허용하지 않는다.
값형 변수에 null값을 지정할 수 있는 방법을 제공하여 값형이나 구조체를 사용하는 프로그램에서 null상태를 나타낼 수 있다. 주로 값형 변수가 null인지 아닌지를 확인하고 처리해야 할 때 유용하다.
형식은 ?
연산자를 사용하여 선언 된다. 예를 들어, int?
는 int형식에 null을 할당할 수 있는 Nullable<int>
형식을 나타낸다.
// Nullable 형식 변수 선언
int? nullableInt = null;
double? nullableDouble = 3.14;
bool? nullableBool = true;
// 값 할당 및 접근
nullableInt = 10;
int intValue = nullableInt.Value;
// null 값 검사
if (nullableDouble.HasValue)
{
Console.WriteLine("nullableDouble 값: " + nullableDouble.Value);
}
else
{
Console.WriteLine("nullableDouble은 null입니다.");
}
// null 병합 연산자 사용
// nullableInt ?? 0과 같이 사용되며, nullableInt가 null이면 0을 반환합니다.
int nonNullableInt = nullableInt ?? 0;
Console.WriteLine("nonNullableInt 값: " + nonNullableInt);
문자열 조작 :
StringBuilder는 Append(), Insert(), Replace(), Remove()등 다양한 메서드를 제공하여 문자열에 대한 추가, 삽입, 치환, 삭제 작업을 수행할 수 있다.
가변성 :
StringBuilder는 내부 퍼버를 사용하여 조작을 수행하므로 크기를 동적으로 조정할 수 있다. 따라서 문자열의 크기가 늘어나거나 줄어들어도 추가적인 메모리 할당이 발생하지 않는다.
효율적인 메모리 관리 :
문자열 조작시 StringBuilder는 내부 버퍼를 사용하여 문자열을 조작하므로, 반복적인 문자열 조작 작업이 발생해도 메모리 할당 및 해제 오버헤드가 크게 감소한다.
Append : 문자열을 뒤에 추가한다.
Insert : 문자열을 지정한 위치에 삽입한다.
Remove : 지정한 위치에서 문자열을 제거한다.
Replace : 문자열의 일부를 다른 문자열로 대체한다.
Clear : StringBuilder의 내용을 모두 지운다.
StringBuilder sb = new StringBuilder();
// 문자열 추가
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
// 문자열 삽입
sb.Insert(5, ", ");
// 문자열 치환
sb.Replace("World", "C#");
// 문자열 삭제
sb.Remove(5, 2);
// 완성된 문자열 출력
string result = sb.ToString();
Console.WriteLine(result);