C# 문법 정리

DF·2024년 1월 12일
post-thumbnail

C# 실행 순서

  1. 코드 작성
    개발자가 C# 언어로 소스 코드를 작성한다. 이 코드는 .cs 확장자를 가진 파일에 저장됨

  2. 컴파일
    C# 컴파일러(csc.exe)는 소스 코드 파일을 컴파일하여 중간 언어(MSIL, Microsoft Intermediate Language, 현재는 IL, Intermediate Language라고 불림)로 변환된 어셈블리를 생성한다. 이 어셈블리는 .dll 또는 .exe 파일 형태로 저장됨.

  3. 어셈블리 생성
    컴파일러가 생성한 어셈블리는 메타데이터(타입 정보, 버전 정보, 외부 어셈블리 참조 등)와 함께 IL 코드를 포함합니다. IL 코드는 플랫폼 독립적이므로, 다양한 환경에서 실행될 수 있는 이점이 있습니다.

  4. 런타임 실행
    어플리케이션을 실행하면, .NET Common Language Runtime(CLR)이 어셈블리를 로드합니다. CLR은 JIT 컴파일러(Just-In-Time 컴파일러)를 사용하여 IL 코드를 실행할 수 있는 기계어로 변환. 이 변환은 런타임에 일어나며, 해당 기계어는 호스트 시스템의 CPU가 이해할 수 있는 명령어이다.

  5. 관리 코드 실행
    CLR은 코드의 실행을 관리한다. 이는 메모리 관리, 스레드 관리, 예외 처리, 가비지 컬렉션 등을 포함합니다. CLR은 .NET 프레임워크의 핵심 구성 요소이며, 코드가 안전하고 효율적으로 실행되도록 한다.

  6. 어셈블리 로드와 실행
    프로그램이 실행되는 동안, 필요한 추가 어셈블리나 리소스는 필요할 때 동적으로 로드된다. 이 과정에서도 CLR은 코드를 관리하고, 보안과 일관된 실행 환경을 제공한다.

데이터 형식(Data type)

: 데이터의 유형과 크기를 지정하는 것

  1. 기본 데이터 형식

  2. 복합 데이터 형식

    • 클래스
    • 구조체
    • 인터페이스 등

메모리

  • 스택(Stack)
    • 기본 데이터 형식을 담는 메모리(값 형식)
    • 데이터를 쌓아 올리는 구조의 메모리
    • 쌓인 순서의 역순으로 필요없는 데이터를 자동으로 제거함(자동 메모리)
    • 값을 저장해 놓고 소스코드가 종료되면 마지막에 저장된 값부터 데이터가 자동으로 삭제된다.
  • 힙(Heap)
    • 복합 데이터 형식을 담는 메모리(참조 형식)
    • 자유롭게 데이터를 저장할 수 있는 메모리
    • 힙에서 데이터의 값을 할당해 놓으면, 스택에서 데이터가 담긴 힙 메모리의 주소를 가리키는 참조의 위치를 찾는 구조

흐름제어

  1. 분기문
    : if / switch
  2. 반복문
    : while / do while / for
  3. 점프문
    : break / continue / goto / reutrn / throw

패턴매칭

// 공부 필요

네임스페이스

: 코드 내의 다양한 요소들(클래스, 구조체, 인터페이스, 열거형 등)을 논리적으로 그룹화하고 이름 충돌을 방지함. 네임스페이스를 통해 동일한 이름의 클래스나 메소드가 다른 네임스페이스에서 독립적으로 존재할 수 있음

  • 선언방법
namespace MyNamespace
{
    class MyClass
    {
        // 클래스 구현
    }
}

namespace AnotherNamespace
{
    class MyClass
    {
        // 다른 구현
    }
}
  • using 키워드를 사용하여 네임스페이스 안에 있는 타입을 전체 경로 없이 임포트할 수 있음
    using MyNamespace;

메소드

  • 메소드 선언 : 클래스 내부에 선언
  • 메소드 선언 필수 요소:
    1. 반환형식
    2. 메소드 이름
    3. 매개변수 목록
class Calculator
{
	public static int Plus(int a, int b)
    {
    	int result = a + b;
        return result;
    }
}

Calculator.plus(1, 2);
// => 3

키워드

접근 제어자

  • public
    :클래스 외부 어디서나 접근 가능
  • private
    : 선언된 클래스 내에서만 접근 가능
  • protected
    : 선언된 클래스와 파생된 클래스 내에서 접근 가능
  • internal
    : 같은 어셈블리 내의 모든 클래스에서 접근 가능
  • protected internal
    : 같은 어셈블리, 또는 파생 클래스에서 접근 가능
  • private protected
    : 선언된 클래스와 동일한 어셈블리 내의 파생 클래스에서만 접근 가능

메서드 및 클래스 관련 키워드

  • static
    : 메서드가 클래스의 인스턴스를 생성하지 않고도 메서드를 바로 사용할 수 있게함
  • void
    : 메서드가 반환 값이 없음을 나타냄
  • const
    : 상수, 즉 변경할 수 없는 값을 정의
  • readonly
    : 읽기 전용 필드를 정의. 선언 시 또는 생성자에서만 값을 할당할 수 있음
  • virtual
    : 파생 클래스에서 재정의(override)할 수 있는 메서드를 정의
  • override
    : 상속받은 클래스에서 부모 클래스의 메서드를 재정의
  • abstract
    : 파생 클래스에서 구현해야 하는 추상 메서드나 클래스를 정의

기타 유용한 키워드

  • using
    : 네임스페이스를 참조하거나, IDisposable 인터페이스를 구현하는 객체를 위한 using 문을 정의
  • new
    : 객체 인스턴스를 생성하거나, 상속받은 멤버를 숨기는 데 사용
  • this
    : 현재 인스턴스에 대한 참조
  • base
    : 부모 클래스에 대한 참조
  • typeof
    : 타입의 System.Type 객체를 얻는 데 사용
  • nameof
    : 코드에서 변수, 클래스, 메서드 등의 이름을 문자열로 가져옴

열거형식

: enum으로 이름이 열거된 한 그룹을 만들어 연속된 상수의 값을 할당하는 형식으로, 각 멤버 값은 기본적으로 정수형(int)으로 저장됨

  • 선언: enum 키워드를 사용하여 선언
  • 접근: Enum의 이름을 사용하여 해당 멤버에 접근
  • 활용: switch문, 조건문 등에서 Enum을 사용하여 코드의 명확성 높힘
enum number { a, b, c, d, e };

// a=0, b=1, c=2, d=3, e=4

클래스

  • 클래스 생성
    : class 키워드를 선언하여 생성함
class Person
{
	// 속성
    public string Name;
    public int Age;
    
    // 메서드
    public void Hi()
    {
    	Console.WriteLine("안녕 나는" + Name + Age + "살이야" );
    }
}
  • 인스턴스 생성
    : new 키워드를 선언하여 생성
// 인스턴스 생성
Person John = new Person();

// 인스턴스 속성에 접근
John.Name = "존 ";
John.Age = "25";

John.Hi();
// 안녕 나는 존 25살이야

생성자

: 클래스 이름과 클래스 내부의 메서드 이름을 동일하게 설정하여, 인스턴스를 생성할 때 메서드가 자동으로 호출되게 함

  • Why? 데이터 베이스 연결을 초기화 하겨나, 필드에 기본값을 설정하기 위함
class Cat
{
	public string Name;
    public Cat(string name) 
    {
    	Name = name;
        Console.WriteLine(Name);
    }
}

Cat myCat = new Cat("나비");

// "나비" 

오버로딩

: 하나의 클래스 내에 동일한 이름의 메서드나 생성자를 여러 개 정의할 수 있는 기능

  • 같은 이름을 가진 메서드나 생성자가 있더라도, 매개변수의 타입, 개수 또는 순서가 다르면 서로 다른 메서드로 취급
  • 반환 타입만 다른 메서드는 오버로딩으로 간주되지 않음
public class Calculator
{
    // 첫 번째 Add 메서드: 두 정수의 합을 계산
    public int Add(int a, int b)
    {
        return a + b;
    }

    // 두 번째 Add 메서드: 세 정수의 합을 계산 (오버로딩)
    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
}

상속

: 클래스가 다른 클래스의 속성과 메서드를 이어 받는 것

  • 상속을 제공하는 클래스를 기본 클래스, 슈퍼 클래스라고 부름
  • 상속 받는 클래스를 파생 클래스, 서브 클래스라고 부름
// 슈퍼 클래스
public class Car
{
	public void StartEngine()
    {
    	Console.WriteLine("부릉 부릉");
    }
}

// 서브 클래스
public class Bus : Car
{
	...
}

// 상속된 메서드 사용
class Program
{
    static void Main(string[] args)
    {
        Bus myBus = new Bus();
        myBus.StartEngine(); // "부릉 부릉" 출력
    }
}

오버라이딩

: 서브 클래스가 슈퍼 클래스로 부터 상속받은 메서드의 구현을 재정의하는 기능

  • 슈퍼 클래스의 메서드 앞에 virtual 키워를 선언함
  • 서브 클래스에서는 override 키워드를 선언하여 메서드를 재정의
  • 서브 클래스에서 오버라이딩하는 메소드는 슈퍼 클래스의 메소드와 동일한 이름, 반환 타입, 매개변수를 가져야함
  • base 키워드를 통해 서브 클래스 내에서 슈퍼 클래스의 멤버에 접근 가능
// 슈퍼 클래스
public class Car
{
	public virtual void StartEngine()
    {
    	Console.WriteLine("부릉 부릉");
    }
}

// 서브 클래스
public class Bus : Car
{
	public override void StartEngine()
    {
    	base.StartEngine(); // 슈퍼 클래스의 StartEngine 호출
    	Console.WriteLine("붕붕");
    }
}

구조체

: 클래스와 비슷하게 여러 데이터(필드, 메소드, 속성 등)를 하나의 단위로 그룹화하지만, 클래스와 다르게 값 형식으로 스택에 저장됨

  • 작은 데이터 그룹을 표현할 때 주로 사용
  • struct 키워드를 선언하여 사용
// 좌표 시스템에서 점을 표현할 때
struct Point
{
    public int X;
    public int Y;

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

튜플

: 튜플은 이름이 없는 구조체로 볼 수 있으며 형식 이름 없이 선언

  • 메소드에서 여러 값을 반환할 때 유용
  • 주로 var 데이터 형식을 사용
(var name, var age) = ("Alice", 30); 
Console.WriteLine(name, age); // "Alice" 30

var person = (Name: "John", Age: 20);
Console.WriteLine(person.Name, person.Age); // "John" 20

인터페이스

: 클래스가 해야하는 행동(메소드)를 결정하는 클래스의 설계도 같은 것

  • 메소드, 속성, 이벤트 등의 시그니처를 정의하지만, 구현을 제공하지 않는 참조 형식
  • 필드는 제공하지 않음
  • 여러 클래스가 동일한 기능을 다른 방식으로 구현해야 할 때 사용
  • interface 키워드를 사용하여 선언
  • I로 시작하도록 명명
// 인터페이스 선언
interface IAnimal
{
    void Speak(string a);
}

// 인터페이스 상속
class Dog : IAnimal
{
    public void Speak(string bark)
    {
        Console.WriteLine(bark);
    }
}

추상클래스

: 추상 클래스는 다른 클래스가 상속받아야만 사용할 수 있는 클래스

  • 추상 클래스는 하나 이상의 추상 메소드(구현이 제공되지 않은 메소드)를 포함
  • 인터페이스와 같이 인스턴스를 직접 생성할 수 없음
  • 인터페이스와 비슷하게 공통적인 기능을 가진 클래스의 베이스로 사용
  • 파생 클래스는 추상 클래스의 추상 메소드를 구현하여 사용
  • abstract 키워드를 선언하여 사용
abstract class Animal
{
    public abstract void Speak(); // 추상 메서드

    public void Move()
    {
        Console.WriteLine("I'm moving");
    }
}

class Cat : Animal
{
	// 추상 메서드 구현
    public override void Speak()
    {
        Console.WriteLine("Meow");
    }
}

프로퍼티

: 프로퍼티(Property)는 클래스, 구조체, 인터페이스 내에서 사용되는 특별한 멤버로, 필드(클래스 또는 구조체의 변수)를 캡슐화하여 필드의 값을 읽고 쓰는 방법을 안전하게 제어할 수 있게 해줌

  • 캡슐화(Encapsulation): 프로퍼티는 필드에 대한 접근을 제어하여(private), 필드의 값을 직접적으로 변경하지 않도록 함
  • 프로퍼티의 구성 요소:
    • get 접근자(Getter): 프로퍼티의 값을 반환하여 값을 읽을 때 호출
    • set 접근자(Setter): 프로퍼티에 값을 할당할 때 호출되며, set 접근자 내에서 value 키워드를 사용하여 할당하려는 값을 참조함
public class Person
{
    private int age;

    public int Age
    {
        get { return age; }
        set
        {
            if (value < 0)
                throw new ArgumentException("존재할 수 없는 나이입니다.");
            age = value;
        }
    }
}

class Program
{
    static void Main()
    {
        Person person = new Person();
        person.Age = 30; // set 접근자를 통해 값을 설정
        Console.WriteLine(person.Age); // get 접근자를 통해 값을 가져옴
    }
}

자동구현 프로퍼티

: 뻔한 코드를 생략하여 사용하는 프로퍼티

  • get;과 set; 접근자만을 명시하여 프로퍼티를 정의
  • 필요한 경우 set 접근자의 접근 수준을 private 로 제한할 수 있음
public class Person
{
    public string Name { get; set; } // 자동 구현 프로퍼티
    public int Age { get; private set; } // set 접근자는 private

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

class Program
{
    static void Main()
    {
        Person person = new Person("Alice", 30);
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");

        person.Name = "Bob"; // Name 프로퍼티는 public set이므로 수정 가능
        // person.Age = 35; // 오류: Age 프로퍼티의 set 접근자는 private이므로 외부에서 수정 불가
    }
}

무명형식

: C#에서 데이터의 일시적인 그룹을 만들기 위해 사용되는 기능으로, 주로 로컬 변수로 사용되어 코드 내에서 임시 데이터를 저장하는 데 사용

  • 무명 형식은 new 키워드와 함께 객체 초기자 구문을 사용하여 프로퍼티 이름과 값을 초기화하여 사용

  • 자동으로 형식 추론을 할 수 있게 var 키워드 사용

  • 무명 형식의 프로퍼티는 기본적으로 읽기 전용으로, 한 번 초기화된 후에는 해당 프로퍼티의 값을 변경할 수 없음

  • 로컬 스코프: 무명 형식은 주로 메소드 내에서 임시 데이터를 저장하는 데 사용되며, 메소드 외부로 전달하기 위해서는 일반적으로 object 타입을 사용하거나 LINQ 쿼리의 일부로 사용됨

var person = new { Name = "Alice", Age = 30 };

Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");

// person.Name = "Bob"; // 컴파일 오류, 무명 형식의 프로퍼티는 읽기 전용

배열

: 같은 형식의 복수 인스턴스를 저장할 수 있는 참조 형식(Heap)으로 연속된 메모리 공간을 차지함

  • 모든 배열은 System.Array 클래스에서 파생

  • System.Array 클래스 유틸리티
    : Sort, BinarySearch, IndexOf, FindIndex, Clear, Foreach, GetLength, Length, Rank 등

  • 생성방법

    // 배열을 생성하는 기본 형식
    데이터형식[] 이름 = new 데이터형식[용량]
    
    // example
    int[] array1 = new int[3] { 1, 2, 3 }; // 기본
    int[] array2 = new int[] { 1, 2, 3 }; // 요소 개수 생략
    int[] array3 = { 1, 2, 3 }; // 간소화

다차원 배열 - 2차원 배열

: 2개의 차원, 행이 2개인 배열

  • 생성방법

    데이터타입[,] 이름 = new 데이터타입[행 개수, 열 개수] {{...}, {...}};
    
    // example
    int[,] array1 = new int[2, 3] {{ 1, 2, 3 }, { 4, 5, 6 }};
    

가변 배열

: 배열의 배열, 배열을 요소로 갖는 배열

  • 생성방법

    데이터형식 [][] 이름 = new 데이터형식[가변배열용량][];
    
    // example
    int[][] array1 = new int[3][];

콜렉션

: 데이터 그룹을 저장하고 관리하기 위한 클래스의 모음

  • ArrayList
    • 생성방법

      // Collections 네임스페이스에서 가져옴
      using System.Collections; 
      
      ArrayList 이름 = new ArrayList();
    • ArrayList는 object 타입을 저장하기 때문에, 정수, 문자열, 객체 등 어떠한 타입의 데이터도 저장할 수 있음

    • 기존 Array와 다르게 배열의 크기를 지정하지 않음

    • Add() Remove() Insert() 메서드로 접근 가능

  • List
    • 생성방법

      using System.Collections;
      
      List<T> 이름 = new List<T>();
    • ArrayList와 비슷하지만 제네릭을 지원

  • Queue
    • 생성방법

      using System.Collections; 
      
      Queue 이름 = new Queue();
    • FIFO (First In, First Out) 방식으로 요소를 저장

    • 주로 데이터가 순차적으로 처리되어야 할 때 사용

    • Enqueue() Dequeue() 메서드로 접근 가능

  • Stack
    • 생성방법

      using System.Collections; 
      
      Stack 이름 = new Stack();
    • LIFO (Last In, First Out) 방식으로 요소를 저장

    • 호출 스택, 역순 탐색 등에 사용

    • Pop() Push() 메서드로 접근 가능

  • Hashtable
    • 생성방법

      using System.Collections; 
      
      Hashtable 이름 = new Hashtable();
      이름["a"] = 1;
      이름["b"] = 2;
      이름["c"] = 3;
      
    • 키와 값의 쌍으로 구성(키는 고유해야 함)

    • 키를 사용하여 값에 접근

    • 현대 C#에서는 Hashtable 대신 제네릭 Dictionary<TKey, TValue> 클래스의 사용이 권장

예외처리

: 프로그램 실행 중에 발생할 수 있는 예외적인 상황(에러)을 관리하고, 프로그램의 정상적인 흐름을 유지하기 위한 방법

  • try 블록
    • 예외가 발생할 수 있는 코드를 포함
    • try 블록 내에서 예외가 발생하면 즉시 해당 블록을 빠져나와 catch 블록으로 이동
  • catch 블록
    • 발생한 예외를 처리
    • 하나의 try 블록에 여러 catch 블록을 둘 수 있음
  • finally 블록
    • 예외 발생 여부와 관계없이 실행되는 코드를 포함
    • 주로 리소스 해제나 정리 작업에 사용
    • finally 블록은 선택적으로 사용할 수 있으며 항상 마지막에 실행
  • throw 문
    • 의도적으로 예외를 발생시키는 데 사용
    • 특정 조건에서 예외를 발생시키거나, 잡힌 예외를 다시 상위로 전달할 때 사용
try
{
    // 예외가 발생할 수 있는 코드
    int a = 10;
    int b = 0;
    
    if(a > 10) {
    	int result = a / b;
    }
    else {
 		// 예외처리되어 throw문이 실행되고 범용catch문으로 넘어감   
    	throw new Exception("a가 10보다 작습니다.");
    }
}
catch (DivideByZeroException ex)
{
    // 특정 예외 처리
    Console.WriteLine("0으로 나눌 수 없습니다: " + ex.Message);
}
catch (Exception ex)
{
    // 모든 예외 처리
    Console.WriteLine("예외 발생: " + ex.Message);
}
finally
{
    // 항상 실행되는 코드 (예: 리소스 해제)
    Console.WriteLine("처리 완료");
}

제네릭(일반화)

: 제네릭 타입 매개변수는 특정 타입을 가리키는 '자리 표시자'와 같은 역할로, 제네릭 타입을 사용하는 클래스나 메서드는 이 매개변수에 어떤 구체적인 타입이 들어가는지에 따라 행동이 결정됨

  • 기본 데이터 타입
    : int, string, bool과 같은 기본 데이터 타입을 제네릭 매개변수로 사용할 수 있습니다. 예를 들어, List는 정수형 리스트를, List은 문자열 리스트를 나타냅니다.

  • 사용자 정의 클래스 타입
    : 사용자가 정의한 클래스도 제네릭 매개변수로 사용할 수 있습니다. 예를 들어, Guest라는 클래스가 있다면 List는 Guest 객체의 리스트를 나타냅니다.

  • 인터페이스 타입
    : 인터페이스도 제네릭 매개변수로 사용될 수 있습니다. 예를 들어, ILogger에서 HomeController는 클래스 타입이며, 이는 ILogger 인터페이스가 HomeController 클래스에 특화된 로깅 기능을 제공한다는 것을 의미합니다.

일반화 메서드

: 특정한 타입에 국한되지 않고, 다양한 타입에 대해 유연하게 동작할 수 있는 메서드를 정의하는 방식

  • 일반화 메서드 정의
public T MyGenericMethod<T>(T param)
{
    return param;
}

// MyGenericMethod는 일반화 메서드입니다. 
// <T>는 타입 매개변수를 나타내며, 메서드 내에서 T 타입으로 사용됩니다.
  • 일반화 메서드의 사용
    : 일반화 메서드를 호출할 때는 꺾쇠 괄호 안에 타입 인수를 제공하여 사용
int result = MyGenericMethod<int>(5);
string resultString = MyGenericMethod<string>("Hello");

// 같은 메서드지만 타입 인수를 다르게하여 호출하였다.
// 타입인수가 없어도 컴파일러가 타입 추론(type inference)할 수 있다.
  • 일반화 메서드는 오버로딩과 함께 사용될 수 있음
public class MyClass
{
    // 일반화 메서드
    public void Method<T>(T param)
    {
        Console.WriteLine(param);
    }

    // string 타입에 대한 오버로딩 메서드
    public void Method(string param)
    {
        Console.WriteLine(param);
    }
}

대리자

: 메서드를 변수처럼 참조할 수 있게 해주는 타입으로, 메서드를 인자로 전달하거나 변수에 할당할 수 있음

  • 대리자는 참조하고 있는 메서드의 시그니처(반환 타입과 매개변수)가 일치해야 함
  • 대리자는 하나 이상의 메서드를 참조할 수 있어, 메서드를 변수처럼 저장하고 전달하며 실행할 수 있음
  • 사용방법
    • 대리자 선언: public delegate void MyDelegate(string message);
    • 대리자 인스턴스 생성: MyDelegate del = new MyDelegate(MethodToCall);
    • 대리자 호출: del("Hello, World!")
접근한정자 delegate 반환형식 이름 (매개변수목록);

// example
public delegate void MyDelegate(string message);

public class Program
{
    public static void Main()
    {
        MyDelegate del = ShowMessage;
        del("Hello World");
    }

    public static void ShowMessage(string message)
    {
        Console.WriteLine(message);
    }
}
  

멀티케스팅

익명 메소드

이벤트

람다식

  • 식 람다(Expression Lambda)
    : 단일 표현식을 사용하여 값을 반환

    Func<int, int> square = x => x * x;
  • 문 람다(Statement Lambda)
    : 중괄호 {}를 사용하여 여러 문을 포함할 수 있으며, return 문을 사용하여 값을 반환함

    Action<int> printSquare = x => {
        int result = x * x;
        Console.WriteLine(result);
    };
    

Func / Action 대리자

- .NET 라이브러리에 사전 정의되어있는 대리자
- 익명 메소드, 무명 함수 정의를 위해 매번 대리자를 새롭게 정의할 필요 없음
- 일반화와 최대 16개 매개변수를 지원(오버로딩)
  • Func 대리자
    : 반환 값이 있는 대리자

    Func<int, bool> isPositive = number => number > 0;
    • 꺽쇠 안에 마지막(bool)은 반환 값의 데이터 타입이고, 나머지(int)는 입력 값의 데이터 타입
  • Action 대리자
    : 반환 값이 없는 대리자

    Action<int> printSquare = x => {
        int result = x * x;
        Console.WriteLine(result);
    };
    
    • Action 대리자는 반환 값이 없기 때문에 입력 값의 데이터 타입만 받는다

LINQ(Language INtegrated Query)

- 직관적으로 해석하면 C# 언어에 데이터 질의 기능이 통합된 것을 의미한다.
- 데이터 집합에서 원하는 데이터를 찾는 작업

  • 쿼리 구문 (Query Syntax)
    • from: 어떤 데이터 집합에서 불러올 건지
    • where: 필터링 조건을 지정
    • select: 결과로 반환할 데이터 형태를 지정
    • orderby: 결과를 정렬
    • groupby: 결과를 그룹화
    • join: 두 데이터 소스를 연결
var query = from p in people
            where p.Age > 18
            orderby p.LastName descending // 기본적으로 ascending
            select p;

// groupby
from person in people
group person by person.LastName into lastNameGroup
select lastNameGroup

// join
from person in people
join pet in pets on person equals pet.Owner
select new { person.Name, pet.Name }
  • 메서드 구문
    : 람다식을 이용해 사용
var evenNumbers = numbers.Where(n => n % 2 == 0);

0개의 댓글