달력

5

« 2024/5 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
2016. 8. 5. 17:41

클래스 및 구조체 프로그래밍/C#2016. 8. 5. 17:41

클래스 및 구조체

https://msdn.microsoft.com/ko-kr/library/ms173109.aspx


1. 클래스 및 구조체는 .NET Framework 공용 형식 시스템의 두 가지 기본 구문입니다.

2. 클래스 및 구조체는 본질적으로 하나의 논리 단위에 속하는 일련의 데이터와 동작을 캡슐화하는 데이터 구조입니다.

3. 데이터 및 동작은 클래스 또는 구조체의 멤버로 이 항목의 뒷부분에서 설명하는 메서드, 속성, 이벤트 등이 여기에 해당합니다.

4. 클래스 또는 구조체 선언은 런타임에 인스턴스 또는 개체를 만드는 데 사용되는 청사진과 같습니다.

5. Person이라는 클래스 또는 구조체를 정의하는 경우 Person은 해당 형식의 이름이 됩니다.

6. Person 형식의 변수 p를 선언하고 초기화하는 경우 p를 Person의 개체 또는 인스턴스라고 합니다.

7. 동일한 Person 형식의 인스턴스를 여러 개 만들 수 있으며 각 인스턴스의 속성 및 필드는 서로 다른 값을 가질 수 있습니다.

8. 클래스는 참조 형식입니다.

9. 클래스의 개체를 만드는 경우 개체가 할당된 변수에는 해당 메모리에 대한 참조만 포함됩니다.

10. 개체 참조가 다른 변수에 할당되면 새 변수는 원래 개체를 참조합니다.

11. 두 변수는 동일한 데이터를 참조하므로 한 변수를 통해 변경된 내용은 다른 변수에도 적용됩니다.

12. 구조체는 값 형식입니다.

13. 구조체를 만드는 경우 구조체가 할당된 변수에는 구조체의 실제 데이터가 포함됩니다.

14. 구조체를 새 변수에 할당하면 해당 구조체가 복사되므로 새 변수와 원래 변수는 동일한 데이터의 별도 복사본을 보유하게 됩니다.

15. 한 복사본이 변경되어도 다른 복사본은 영향을 받지 않습니다.

16. 일반적으로 클래스는 클래스 개체를 생성한 후 수정하려고 하는 데이터나 더 복잡한 동작을 모델링하는 데 사용됩니다.

17. 구조체는 구조체를 생성한 후 수정하지 않을 데이터를 주로 포함하는 작은 데이터 구조에 더 적합합니다.

18. 자세한 내용은 클래스(C# 프로그래밍 가이드), 개체(C# 프로그래밍 가이드) 및 구조체(C# 프로그래밍 가이드)을 참조하십시오.


예제

1. 다음 예제에서는 ProgrammingGuide 네임스페이스의 최상위에 세 가지 멤버가 있는 MyCustomClass를 정의합니다.

2. MyCustomClass의 인스턴스(개체)는 Program 클래스의 Main 메서드에서 생성되고 개체의 메서드와 속성은 점 표기를 사용하여 액세스됩니다.


namespace ProgrammingGuide
{
    // Class definition.
    public class MyCustomClass
    {
        // Class members:
        // Property.
        public int Number { get; set; }

        // Method.
        public int Multiply(int num)
        {
            return num * Number;
        }

        // Instance Constructor.
        public MyCustomClass()
        {
            Number = 0;
        }
    }
    // Another class definition. This one contains
    // the Main method, the entry point for the program.
    class Program
    {
        static void Main(string[] args)
        {
            // Create an object of type MyCustomClass.
            MyCustomClass myClass = new MyCustomClass();

            // Set the value of a public property.
            myClass.Number = 27;

            // Call a public method.
            int result = myClass.Multiply(4);
        }
    }
}



캡슐화

1. 캡슐화는 개체 지향 프로그래밍의 첫 번째 기둥(pillar) 또는 원칙(principle)으로도 불립니다.

2. 캡슐화 원칙에 따르면 클래스 또는 구조체에서는 클래스 또는 구조체 외부 코드에서 각 멤버로의 액세스 가능성을 지정할 수 있습니다.

3. 클래스 또는 어셈블리 외부에서 사용할 목적이 아닌 메서드 및 변수는 코딩 오류 또는 악의적인 이용의 가능성을 방지하기 위해 숨길 수 있습니다.

4. 클래스에 대한 자세한 내용은 클래스(C# 프로그래밍 가이드) 및 개체(C# 프로그래밍 가이드)를 참조하십시오.


Members

1. 모든 메서드, 필드, 상수, 속성 및 이벤트는 형식 내에서 선언되어야 하는데 이들을 형식의 멤버라고 합니다.

2. C#에는 일부 다른 언어와 달리 전역 변수 또는 메서드가 없습니다.

3. 프로그램의 진입점인 Main 메서드까지도 클래스 또는 구조체 내부에서 선언해야 합니다.

4. 다음 목록에서는 클래스나 구조체에서 선언할 수 있는 다양한 종류의 멤버를 모두 보여 줍니다.




접근 가능성(Accessibility)

1. 일부 메서드 및 속성은 클래스 또는 구조체 외부 코드(클라이언트 코드)에서 호출하거나 액세스하기 위한 것입니다.

2. 다른 메서드 및 속성은 클래스 또는 구조체 자체에서 사용하기 위한 것입니다.

3. 의도된 클라이언트 코드에서만 접근할 수 있도록 코드의 액세스 가능성을 제한해야 합니다.

4. 액세스 한정자인 public, protected, internal, protected internal 및 private을 사용하여 클라이언트 코드에서 형식 및 해당 멤버로의 액세스 가능성을 지정할 수 있습니다.

5. 기본 액세스 가능성은 private입니다.

6. 자세한 내용은 액세스 한정자(C# 프로그래밍 가이드)을(를) 참조하십시오.


상속(Inheritance)

1. 구조체와 달리 클래스는 상속 개념을 지원합니다.

2. 다른 클래스(기본 클래스)를 상속하는 클래스에는 생성자와 소멸자를 제외한 기본 클래스의 모든 public, protected 및 internal 멤버가 자동으로 포함됩니다.

3. 자세한 내용은 상속(C# 프로그래밍 가이드) 및 다형성(C# 프로그래밍 가이드)을 참조하십시오.

4. 클래스를 abstract로 선언할 수도 있습니다.

5. 이것은 해당 클래스에서 하나 이상의 메서드가 구현되지 않았다는 의미입니다.

6. 추상 클래스는 직접 인스턴스화할 수 없지만 다른 클래스의 기본 클래스로 사용하여 누락된 구현을 제공할 수 있습니다.

7. 클래스를 sealed로 선언할 수도 있습니다.

8. 그러면 다른 클래스에서 해당 클래스를 상속할 수 없습니다.

9. 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버(C# 프로그래밍 가이드)을(를) 참조하십시오.


인터페이스(Interface)

1. 클래스와 구조체는 여러 인터페이스를 상속할 수 있습니다.

2. 인터페이스를 상속하는 것은 형식에서 해당 인터페이스에 정의된 모든 메서드를 구현한다는 의미입니다.

3. 자세한 내용은 인터페이스(C# 프로그래밍 가이드)을(를) 참조하십시오.


제네릭 형식(Generic Types)

1. 클래스와 구조체를 하나 이상의 형식 매개 변수와 함께 정의할 수 있습니다.

2. 클라이언트 코드에서는 형식의 인스턴스를 생성할 때 형식을 지정합니다.

3. 예를 들어 System.Collections.Generic 네임스페이스의 List<T> 클래스에는 형식 매개 변수 하나가 정의되어 있습니다.

4. 클라이언트 코드에서는 List<string> 또는 List<int> 인스턴스를 생성하여 목록에 유지되는 형식을 지정합니다. 


정적 형식(Static Types)

1. 클래스(구조체는 안 됨)를 static으로 선언할 수 있습니다.

2. 정적 클래스는 정적 멤버만 포함할 수 있으며 new 키워드를 사용하여 인스턴스화할 수 없습니다.

3. 따라서 프로그램이 로드될 때 클래스 복사본 하나가 로드되고 클래스 이름을 통해 해당 멤버에 액세스합니다.

4. 클래스 및 구조체는 모두 정적 멤버를 포함할 수 있습니다.


중첩 형식(Nested Types)

1. 클래스 또는 구조체는 다른 클래스 또는 구조체 내에 중첩될 수 있습니다.


부분 형식(Partial Types)

1. 한 코드 파일에서 클래스, 구조체 또는 메서드의 일부를 정의하고 다른 코드 파일에서 나머지 부분을 정의할 수 있습니다.


개체 이니셜라이저(Object Initializers)

1. 생성자를 명시적으로 호출하지 않고도 클래스 또는 구조체 개체와 개체 컬렉션을 인스턴스화하고 초기화할 수 있습니다.


익명 형식(Anonymous Types)

1. 목록을 데이터 구조로 채우지만 해당 목록을 유지하거나 다른 메서드로 전달할 필요가 없는 경우와 같이, 명명된 클래스를 만드는 것이 불편하거나 만들 필요가 없는 경우에는 익명 형식을 사용합니다.


확장 메서드(Extension Methods)

1. 원래 형식에서 마치 해당 형식에 속한 것처럼 호출할 수 있는 메서드가 있는 별도의 형식을 만들면 파생 클래스를 만들지 않고도 클래스를 "확장"할 수 있습니다.


암시적으로 형식화한 지역 변수(Implicitly Types Local Variables)

1. 클래스 또는 구조체 메서드 내에서 암시적 형식 지정을 사용하여 컴파일러가 컴파일 타임에 올바른 형식을 결정하게 만들 수 있습니다.




클래스


1. 클래스는 다른 형식의 변수, 메서드 및 이벤트를 그룹화하여 사용자 지정 형식을 만들 수 있는 구문입니다.

2. 클래스는 청사진과 같습니다.

3. 클래스는 형식의 데이터 및 동작을 정의합니다.

4. 클래스가 static으로 선언되지 않은 경우 클라이언트 코드에서는 변수에 할당되는 개체 또는 인스턴스를 만들어 클래스를 사용할 수 있습니다.

5. 변수는 해당 변수에 대한 모든 참조가 범위를 벗어날 때까지 메모리에 유지됩니다.

6. 모든 참조가 범위를 벗어나면 CLR에서 해당 변수를 가비지 수집 대상으로 표시합니다.

7. 클래스가 static으로 선언되면 하나의 복사본만 메모리에 존재하며 클라이언트 코드에서는 인스턴스 변수를 통해서가 아니라 클래스 자체를 통해서만 클래스에 액세스할 수 있습니다.

8. 자세한 내용은 정적 클래스 및 정적 클래스 멤버(C# 프로그래밍 가이드)을 참조하십시오.

9. 구조체와 달리 클래스는 개체 지향 프로그래밍의 근본 특징인 상속을 지원합니다.

10. 자세한 내용은 상속(C# 프로그래밍 가이드)을 참조하십시오.


클래스 선언

1. 다음 예제와 같이 클래스는 class 키워드를 사용하여 선언합니다.


public class Customer
{
    //Fields, properties, methods and events go here...
}


2. class 키워드는 액세스 수준 뒤에 사용합니다.

3. 이 경우에는 public을 사용했기 때문에 모든 사용자가 이 클래스에서 개체를 만들 수 있습니다.

4. 클래스의 이름은 class 키워드 뒤에 사용합니다.

5. 정의의 나머지 부분인 클래스 본문에서는 동작과 데이터를 정의합니다.

6. 클래스의 필드, 속성, 메서드 및 이벤트를 통칭하여 클래스 멤버라고 합니다.


개체 만들기

1. 클래스와 개체는 서로 바꿔 사용되기도 하지만 근본적으로 이 둘은 서로 다릅니다.

2. 클래스는 개체의 형식을 정의할 뿐 개체 자체는 아닙니다.

3. 개체는 클래스를 기반으로 하는 구체적인 엔터티이며 클래스의 인스턴스라고도 합니다.

4. 개체는 다음과 같이 new 키워드 뒤에 개체의 기반으로 사용할 클래스의 이름을 추가하여 만들 수 있습니다.


Customer object1 = new Customer();


5. 클래스의 인스턴스가 만들어지면 개체에 대한 참조가 프로그래머에게 다시 전달됩니다.

6. 위의 예제에서 object1은 Customer를 기반으로 하는 개체에 대한 참조입니다.

7. 이 참조는 새 개체를 가리키지만 개체 데이터 자체를 포함하지는 않습니다.

8. 사실 개체를 전혀 만들지 않고도 개체 참조를 만들 수 있습니다.


Customer object2;


9. 이와 같이 개체를 참조하지 않는 개체 참조는 만들지 않는 것이 좋습니다.

10. 이러한 참조를 통해 개체에 액세스하려고 하면 런타임에 오류가 발생하기 때문입니다.

11. 그러나 다음과 같이 새 개체를 만들거나 이러한 참조를 기존 개체에 할당하여 참조가 개체를 가리키도록 할 수 있습니다.


Customer object3 = new Customer();
Customer object4 = object3;


12, 이 코드에서는 모두 동일한 개체를 가리키는 두 개의 개체 참조를 만듭니다.

13. 따라서 object3을 통해 개체에 대해 변경한 내용은 모두 이후에 object4를 사용할 때 반영됩니다.

14. 클래스를 기반으로 하는 개체는 참조를 통해 참조되기 때문에 클래스를 참조 형식이라고 합니다.


클래스 상속

1. 상속(Inheritance)은 파생(derivation)을 통해 이루어집니다.

2. 즉, 클래스는 데이터와 동작을 상속할 기본 클래스를 사용하여 선언합니다.

3. 기본 클래스는 다음과 같이 파생 클래스 이름 뒤에 콜론과 기본 클래스의 이름을 추가하여 지정합니다.


public class Manager : Employee
{
    // Employee fields, properties, methods and events are inherited
    // New Manager fields, properties, methods and events go here...
}


4. 클래스에서 기본 클래스를 선언하면 생성자를 제외한 기본 클래스의 모든 멤버가 상속됩니다.

5. C++와 달리 C#의 클래스는 하나의 기본 클래스만 직접 상속할 수 있습니다.

6. 그러나 기본 클래스 자체도 다른 클래스를 상속할 수 있기 때문에 클래스는 여러 개의 기본 클래스를 간접적으로 상속할 수 있습니다.

7. 또한 클래스는 인터페이스를 두 개 이상 직접 구현할 수 있습니다.

8. 자세한 내용은 인터페이스(C# 프로그래밍 가이드)을 참조하십시오.

9. 클래스를 abstract로 선언할 수 있습니다.

10. 추상 클래스는 시그니처 정의가 있지만 구현은 없는 추상 메서드를 포함합니다.

11. 추상 클래스는 인스턴스화할 수 없습니다.

12. 추상 클래스는 추상 메서드를 구현하는 파생 클래스를 통해서만 사용할 수 있습니다.

13. 이와 반대로 sealed 클래스는 다른 클래스에서 상속할 수 없습니다.

14. 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버(C# 프로그래밍 가이드)을 참조하십시오.

15. 클래스 정의는 여러 소스 파일로 분할할 수 있습니다.

16. 자세한 내용은 Partial 클래스 및 메서드(C# 프로그래밍 가이드)을 참조하십시오.


설명

1. 다음 예제에서는 단일 필드, 메서드 및 생성자라는 특별한 메서드가 포함된 공용 클래스를 정의합니다.

2. 자세한 내용은 생성자(C# 프로그래밍 가이드)을 참조하십시오.

3. 그런 다음 new 키워드를 사용하여 클래스를 인스턴스화합니다.


예제

public class Person
{
    // Field
    public string name;

    // Constructor that takes no arguments.
    public Person()
    {
        name = "unknown";
    }

    // Constructor that takes one argument.
    public Person(string nm)
    {
        name = nm;
    }

    // Method
    public void SetName(string newName)
    {
        name = newName;
    }
}
class TestPerson
{
    static void Main()
    {
        // Call the constructor that has no parameters.
        Person person1 = new Person();
        Console.WriteLine(person1.name);

        person1.SetName("John Smith");
        Console.WriteLine(person1.name);

        // Call the constructor that has one parameter.
        Person person2 = new Person("Sarah Jones");
        Console.WriteLine(person2.name);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output:
// unknown
// John Smith
// Sarah Jones




개체


1. 클래스나 구조체의 정의는 해당 형식으로 수행할 수 있는 작업을 지정하는 청사진과 같습니다.

2. 개체는 기본적으로 이 청사진에 따라 할당되고 구성된 메모리 블록입니다.

3. 프로그램은 동일한 클래스의 개체를 많이 만들 수 있습니다.

4. 개체는 인스턴스라고도 하며 명명된 변수나 배열 또는 컬렉션에 저장될 수 있습니다.

5. 클라이언트 코드는 이러한 변수를 사용하여 메서드를 호출하고 개체의 공용 속성에 액세스하는 코드입니다.

6. C#과 같은 개체 지향 언어에서 일반적인 프로그램은 동적으로 상호 작용하는 여러 개체로 구성되어 있습니다.


구조체 인스턴스 VS. 클래스 인스턴스

1. 클래스는 참조 형식이므로 클래스 개체의 변수는 개체 주소에 대한 참조를 관리되는 힙에 유지합니다.

2. 첫 번째 개체에 동일한 형식의 두 번째 개체가 할당되면 두 변수 모두 같은 주소에 있는 개체를 참조합니다.

3. 이 내용에 대해서는 이 항목의 뒷부분에서 자세히 설명합니다.

4. 클래스의 인스턴스는 new 연산자를 사용하여 생성됩니다.

5. 다음 예제에서 Person은 형식이고 person1 및 person 2는 해당 형식의 인스턴스 또는 개체입니다.


public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    //Other properties, methods, events...
}

class Program
{
    static void Main()
    {
        Person person1 = new Person("Leopold", 6);
        Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name, person1.Age);

        // Declare  new person, assign person1 to it.
        Person person2 = person1;

        //Change the name of person2, and person1 also changes.
        person2.Name = "Molly";
        person2.Age = 16;

        Console.WriteLine("person2 Name = {0} Age = {1}", person2.Name, person2.Age);
        Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name, person1.Age);

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();

    }
}
/*
    Output:
    person1 Name = Leopold Age = 6
    person2 Name = Molly Age = 16
    person1 Name = Molly Age = 16
*/


6. 구조체는 값 형식이므로 구조체 개체의 변수는 전체 개체의 복사본을 유지합니다.

7. 다음 예제와 같이 new 연산자를 사용하여 구조체의 인스턴스를 만들 수 있지만 이 연산자가 반드시 필요한 것은 아닙니다.


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

public class Application
{
    static void Main()
    {
        // Create  struct instance and initialize by using "new".
        // Memory is allocated on thread stack.
        Person p1 = new Person("Alex", 9);
        Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);

        // Create  new struct object. Note that  struct can be initialized
        // without using "new".
        Person p2 = p1;

        // Assign values to p2 members.
        p2.Name = "Spencer";
        p2.Age = 7;
        Console.WriteLine("p2 Name = {0} Age = {1}", p2.Name, p2.Age);

        // p1 values remain unchanged because p2 is  copy.
        Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/*
  Output:
    p1 Name = Alex Age = 9
    p2 Name = Spencer Age = 7
    p1 Name = Alex Age = 9
*/


8. p1 및 p2에 대한 메모리는 스레드 스택에 할당됩니다.

9. 이 메모리는 해당 구조체가 선언된 형식 또는 메서드와 함께 회수됩니다.

10. 이것이 구조체가 할당 시 복사되는 이유 중 하나입니다.

11. 이와 달리 클래스 인스턴스용으로 할당된 메모리는 개체에 대한 모든 참조가 범위를 벗어나면 공용 언어 런타임에 의해 자동으로 회수(가비지 수집)됩니다.

12. 하지만 C++에서와 같이 클래스 개체를 명확하게 삭제할 수는 없습니다.

13. .NET Framework의 가비지 수집에 대한 자세한 내용은 Garbage Collection을 참조하십시오.


참고

1. 공용 언어 런타임에서 관리되는 힙의 메모리 할당 및 할당 취소는 고도로 최적화되어 있습니다.

2. 대부분의 경우 힙에 클래스 인스턴스를 할당하는 것과 스택에 구조체 인스턴스를 할당하는 것 간에 성능상의 차이는 거의 없습니다.



VS Identity 개체. 값이 같은지 확인

1. 두 개체가 같은지 비교할 때에는 두 변수가 메모리의 동일한 개체를 나타내는지를 비교할 것인지 또는 개체 필드에 있는 하나 이상의 값이 같은지를 비교할 것인지를 구분해야 합니다.

2. 값을 비교하려는 경우에는 개체가 값 형식의 인스턴스(구조체)인지 참조 형식의 인스턴스(클래스, 대리자, 배열)인지 고려해야 합니다.

3. 두 클래스 인스턴스가 메모리에서 동일한 위치를 참조하는지 확인하려면, 즉 동일한 ID를 갖는지 확인하려면 정적 Equals 메서드를 사용합니다.

4. (System.Object는 사용자 정의 구조체 및 클래스를 비롯한 모든 값 형식 및 참조 형식에 대한 암시적 기본 클래스입니다.)

5. 두 구조체 인스턴스의 인스턴스 필드가 동일한 값을 갖는지 확인하려면 ValueType.Equals 메서드를 사용합니다.

6. 모든 구조체는 System.ValueType에서 암시적으로 상속되므로 다음 예제와 같이 개체에서 직접 이 메서드를 호출합니다.


// Person is defined in the previous example.

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

Person p1 = new Person("Wallace", 75);
Person p2;
p2.Name = "Wallace";
p2.Age = 75;

if (p2.Equals(p1))
    Console.WriteLine("p2 and p1 have the same values.");

// Output: p2 and p1 have the same values.


7. 구조체에서 필드의 내용을 확인할 수 있어야 하므로 Equals의 System.ValueType 구현에서는 리플렉션을 사용합니다.

8. 고유한 구조체를 만들 때 해당 형식과 관련된 효율적인 동일성 비교 알고리즘을 제공하려면 Equals 메서드를 재정의합니다.

9. 두 개의 클래스 인스턴스에서 필드의 값이 같은지 확인하려면 Equals 메서드 또는 == 연산자를 사용할 수도 있습니다.

10. 그러나 해당 형식의 개체에 대해 어떤 "품질"인지에 대한 사용자 지정 정의를 제공하기 위해 클래스가 오버로드된 경우에만 사용합니다.

11. 이 클래스는 IEquatable<T> 인터페이스 또는 IEqualityComparer<T> 인터페이스를 구현할 수도 있습니다.

12. 두 인터페이스 모두 값이 같은지 테스트하는 데 사용할 수 있는 메서드를 제공합니다.

13. Equals를 재지정하는 자체 클래스를 설계할 때 방법: 형식의 값 일치 정의(C# 프로그래밍 가이드) 및 Object.Equals(Object)에 명시된 지침에 따라야 합니다.




구조체


1. 구조체는 다음 예제와 같이 struct 키워드를 사용하여 정의합니다.


public struct PostalAddress
{
    // Fields, properties, methods and events go here...
}


구조체는 클래스와 동일한 구문을 대부분 공유하지만 클래스보다 제한적입니다.


1. 구조체 선언 내에서 필드는 const 또는 static으로 선언한 경우에만 초기화할 수 있습니다.

2. 구조체에서는 매개 변수가 없는 생성자인 기본 생성자나 소멸자를 선언할 수 없습니다.

3. 구조체는 할당 시 복사됩니다. 구조체가 새 변수에 할당되면 모든 데이터가 복사되고, 새 복사본을 수정해도 원래 4. 복사본의 데이터는 변경되지 않습니다. Dictionary<string, myStruct>와 같은 값 형식의 컬렉션을 사용할 때 이것이 중요합니다.

5. 구조체는 값 형식이고 클래스는 참조 형식입니다.

6. 클래스와 달리 구조체는 new 연산자를 사용하지 않고 인스턴스화할 수 있습니다.

7. 구조체는 매개 변수가 있는 생성자를 선언할 수 있습니다.

8. 구조체는 다른 구조체 또는 클래스에서 상속될 수 없으며, 클래스의 기본 클래스가 될 수도 없습니다. 모든 구조체는 System.Object를 상속하는 System.ValueType에서 직접 상속합니다.

9. 구조체는 인터페이스를 구현할 수 있습니다.

10. 구조체를 nullable 형식으로 사용할 수 있고 여기에 null 값을 할당할 수 있습니다.




구조체 사용


1. struct 형식은 Point, Rectangle, Color 등의 간단한 개체를 나타내는 데 적합합니다.

2. 점을 자동으로 구현된 속성이 있는 클래스로 표현할 수도 있지만 일부 시나리오에서는 구조체를 사용하는 것이 더 효율적일 수 있습니다.

3. 예를 들어 1000개의 Point 개체가 있는 배열을 선언하는 경우에는 각 개체를 참조하기 위해 추가 메모리를 할당하게 되며, 이러한 경우 구조체가 보다 효율적입니다.

4. .NET Framework에 Point라는 개체가 포함되어 있으므로 이 예제의 구조체 이름은 "CoOrds"로 지정되었습니다.


public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}



5. 구조체에 대해 기본 생성자(매개 변수 없음)를 정의하면 오류가 발생합니다.

6. 구조체 본문에서 인스턴스 필드를 초기화해도 오류가 발생합니다.

7. 구조체의 멤버는 매개 변수가 있는 생성자를 사용하거나 구조체가 선언된 후 멤버에 개별적으로 액세스하는 방법으로만 초기화할 수 있습니다.

8. 전용 멤버 또는 다른 방법으로 액세스할 수 없는 멤버는 생성자 내부에서만 초기화할 수 있습니다.

9. new 연산자를 사용하여 구조체 개체를 생성할 경우 구조체 개체가 생성된 후에 적절한 생성자가 호출됩니다.

10. 클래스와 달리 구조체는 new 연산자를 사용하지 않고 인스턴스화할 수 있습니다.

11. 이런 경우에는 생성자를 호출하지 않으므로 할당이 더 효율적으로 이루어집니다.

12. 하지만 필드가 할당되지 않은 상태로 남아 있게 되며 개체를 사용하려면 모든 필드를 초기화해야 합니다.

13. 구조체에 참조 형식인 멤버가 있는 경우 멤버의 기본 생성자가 명시적으로 호출되어야 합니다.

14. 그렇지 않으면 멤버가 할당되지 않은 상태로 남아 있게 되므로 구조체를 사용할 수 없습니다.

15. 이 경우 컴파일러 오류 CS0171이 발생합니다.

16. 클래스의 경우와는 달리 구조체에 대한 상속은 없습니다.

17. 구조체는 다른 구조체 또는 클래스에서 상속될 수 없으며, 클래스의 기본 클래스가 될 수도 없습니다.

18. 그러나 구조체는 기본 클래스 Object에서 상속할 수 있습니다.

19. 구조체는 클래스에서 인터페이스를 구현하는 것과 동일한 방식으로 인터페이스를 구현할 수 있습니다.

20. struct 키워드를 사용하여 클래스를 선언할 수 없습니다.

21. C#에서 클래스와 구조체는 구문적으로 다릅니다.

22. 구조체는 값 형식인 반면 클래스는 참조 형식입니다.

23. 자세한 내용은 값 형식을 참조하세요.

24. 참조 형식 구문이 필요한 경우가 아니라면 작은 클래스는 구조체로 대신 선언하면 시스템에서 보다 효율적으로 처리할 수 있습니다.


예제1

1. 이 예제에서는 기본 생성자와 매개 변수가 있는 생성자 둘 다를 사용하여 struct를 초기화하는 방법을 보여 줍니다.

public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}


// Declare and initialize struct objects.
class TestCoOrds
{
    static void Main()
    {
        // Initialize:   
        CoOrds coords1 = new CoOrds();
        CoOrds coords2 = new CoOrds(10, 10);

        // Display results:
        Console.Write("CoOrds 1: ");
        Console.WriteLine("x = {0}, y = {1}", coords1.x, coords1.y);

        Console.Write("CoOrds 2: ");
        Console.WriteLine("x = {0}, y = {1}", coords2.x, coords2.y);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    CoOrds 1: x = 0, y = 0
    CoOrds 2: x = 10, y = 10
*/



예제2

1. 이 예제에서는 구조체의 특징에 대해 설명합니다.

2. 여기서는 new 연산자를 사용하지 않고 CoOrds 개체를 만듭니다.

3. struct를 class로 바꾸면 프로그램이 컴파일되지 않습니다.

public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}


// Declare a struct object without "new."
class TestCoOrdsNoNew
{
    static void Main()
    {
        // Declare an object:
        CoOrds coords1;

        // Initialize:
        coords1.x = 10;
        coords1.y = 20;

        // Display results:
        Console.Write("CoOrds 1: ");
        Console.WriteLine("x = {0}, y = {1}", coords1.x, coords1.y);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: CoOrds 1: x = 10, y = 20




상속


1. 상속은 캡슐화 및 다형성과 함께 개체 지향 프로그래밍의 세 가지 주요 특징 또는 기둥 중 하나입니다.

2. 상속을 사용하면 다른 클래스에 정의된 동작을 다시 사용, 확장 및 수정하는 새 클래스를 만들 수 있습니다.

3. 멤버가 상속되는 클래스를 기본 클래스라고 하고 해당 멤버를 상속하는 클래스를 파생 클래스라고 합니다.

4. 파생 클래스는 직접 기본 클래스를 하나만 가질 수 있습니다.

5. 그러나, 상속은 전이적입니다.

6. ClassC가 ClassB에서 파생되고 ClassB가 ClassA에서 파생되는 경우 ClassC는 ClassB 및 ClassA에서 선언된 멤버를 상속합니다.

7. 개념적으로 파생 클래스는 기본 클래스를 구체화한 것입니다.

8. 예를 들어 기본 클래스 Animal이 있으면 Mammal이라는 하나의 파생 클래스와 Reptile이라는 또 다른 파생 클래스를 만들 수 있습니다.

9. Mammal은 Animal이고 Reptile도 Animal이지만 각 파생 클래스는 기본 클래스의 서로 다른 특징을 나타냅니다.

10. 다른 클래스를 상속하여 파생 클래스를 정의하는 경우 이 클래스는 암시적으로 생성자와 소멸자를 제외한 기본 클래스의 모든 멤버를 얻습니다.

11. 따라서 파생 클래스는 기본 클래스의 코드를 다시 구현하지 않고 재사용할 수 있습니다.

12. 파생 클래스에서는 멤버를 추가하여 기본 클래스의 기능을 확장할 수 있습니다.

13. 다음 그림에서는 특정 비즈니스 프로세스에서 작업 항목을 나타내는 WorkItem 클래스를 보여 줍니다.

14. 모든 클래스처럼 System.Object에서 파생되고 모든 해당 메서드를 상속합니다.

15. WorkItem은 자신의 다섯 멤버를 추가합니다.

16. 생성자는 상속되지 않으므로 생성자를 포함합니다.

17. 클래스 ChangeRequest는 WorkItem에서 상속하며, 특정 종류의 작업 항목을 나타냅니다.

18. ChangeRequest는 WorkItem 및 Object에서 상속되는 멤버에 둘 이상의 멤버를 추가합니다.

19. 자체 생성자를 추가해야 하며 originalItemID도 추가합니다.

20. 속성 originalItemID를 통해 ChangeRequest 인스턴스를 변경 요청이 적용되는 원본 WorkItem과 연결할 수 있습니다.



21. 다음 예제에서는 이전 그림에서 설명한 클래스 관계가 C#에서 어떻게 표현되는지 보여 줍니다.

22. 이 예제에서는 WorkItem이 가상 메서드 Object.ToString을 재정의하는 방법 및 ChangeRequest 클래스가 해당 메서드의 WorkItem 구현을 상속하는 방법도 보여 줍니다.


// WorkItem implicitly inherits from the Object class.
public class WorkItem
{
    // Static field currentID stores the job ID of the last WorkItem that
    // has been created.
    private static int currentID;

    //Properties.
    protected int ID { get; set; }
    protected string Title { get; set; }
    protected string Description { get; set; }
    protected TimeSpan jobLength { get; set; }

    // Default constructor. If a derived class does not invoke a base-
    // class constructor explicitly, the default constructor is called
    // implicitly. 
    public WorkItem()
    {
        ID = 0;
        Title = "Default title";
        Description = "Default description.";
        jobLength = new TimeSpan();
    }

    // Instance constructor that has three parameters.
    public WorkItem(string title, string desc, TimeSpan joblen)
    {
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = joblen;
    }

    // Static constructor to initialize the static member, currentID. This
    // constructor is called one time, automatically, before any instance
    // of WorkItem or ChangeRequest is created, or currentID is referenced.
    static WorkItem()
    {
        currentID = 0;
    }


    protected int GetNextID()
    {
        // currentID is a static field. It is incremented each time a new
        // instance of WorkItem is created.
        return ++currentID;
    }

    // Method Update enables you to update the title and job length of an
    // existing WorkItem object.
    public void Update(string title, TimeSpan joblen)
    {
        this.Title = title;
        this.jobLength = joblen;
    }

    // Virtual method override of the ToString method that is inherited
    // from System.Object.
    public override string ToString()
    {
        return String.Format("{0} - {1}", this.ID, this.Title);
    }
}

// ChangeRequest derives from WorkItem and adds a property (originalItemID) 
// and two constructors.
public class ChangeRequest : WorkItem
{
    protected int originalItemID { get; set; }

    // Constructors. Because neither constructor calls a base-class 
    // constructor explicitly, the default constructor in the base class
    // is called implicitly. The base class must contain a default 
    // constructor.

    // Default constructor for the derived class.
    public ChangeRequest() { }

    // Instance constructor that has four parameters.
    public ChangeRequest(string title, string desc, TimeSpan jobLen,
                         int originalID)
    {
        // The following properties and the GetNexID method are inherited 
        // from WorkItem.
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = jobLen;

        // Property originalItemId is a member of ChangeRequest, but not 
        // of WorkItem.
        this.originalItemID = originalID;
    }
}

class Program
{
    static void Main()
    {
        // Create an instance of WorkItem by using the constructor in the 
        // base class that takes three arguments.
        WorkItem item = new WorkItem("Fix Bugs",
                                     "Fix all bugs in my code branch",
                                     new TimeSpan(3, 4, 0, 0));

        // Create an instance of ChangeRequest by using the constructor in
        // the derived class that takes four arguments.
        ChangeRequest change = new ChangeRequest("Change Base Class Design",
                                                 "Add members to the class",
                                                 new TimeSpan(4, 0, 0),
                                                 1);

        // Use the ToString method defined in WorkItem.
        Console.WriteLine(item.ToString());

        // Use the inherited Update method to change the title of the 
        // ChangeRequest object.
        change.Update("Change the Design of the Base Class",
            new TimeSpan(4, 0, 0));

        // ChangeRequest inherits WorkItem's override of ToString.
        Console.WriteLine(change.ToString());

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    1 - Fix Bugs
    2 - Change the Design of the Base Class
*/


추상 및 가상 메서드

1. 기본 클래스가 메서드를 virtual로 선언하면 파생 클래스는 자체 구현으로 이 메서드를 override할 수 있습니다.

2. 기본 클래스가 멤버를 abstract로 선언하는 경우 해당 클래스를 직접 상속하는 비추상 클래스는 이 메서드를 재정의해야 합니다.

3. 파생 클래스가 추상 클래스이면 추상 멤버를 구현하지 않고 상속합니다.

4. 추상 및 가상 멤버는 개체 지향 프로그래밍의 두 번째 주요 특징인 다형성의 기반이 됩니다.

5. 자세한 내용은 다형성(C# 프로그래밍 가이드)를 참조하십시오.


추상 기본 클래스

1. new 키워드를 사용하여 직접 인스턴스화하지 않으려는 클래스는 abstract로 선언할 수 있습니다.

2. 이렇게 하면 이 클래스는 새 클래스가 자신에게서 파생되는 경우에만 사용될 수 있습니다.

3. 추상 클래스는 추상으로 선언된 하나 이상의 메서드 시그니처를 포함할 수 있습니다.

4. 이러한 시그니처는 매개 변수와 반환 값을 지정하지만 구현(메서드 본문)은 포함하고 있지 않습니다.

5. 추상 클래스가 추상 멤버를 포함할 필요는 없지만 클래스에 추상 멤버가 있는 경우 해당 클래스는 반드시 추상으로 선언되어야 합니다.

6. 추상 클래스가 아닌 파생 클래스는 추상 기본 클래스의 모든 추상 멤버에 대한 구현을 제공해야 합니다.

7. 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버(C# 프로그래밍 가이드)를 참조하십시오.


인터페이스

1. 인터페이스는 추상 멤버로만 구성된 추상 기본 클래스와 어느 정도 유사한 참조 형식입니다.

2. 클래스가 인터페이스를 구현하는 경우 해당 인터페이스의 모든 멤버에 대한 구현을 제공해야 합니다.

3. 클래스는 하나의 직접 기본 클래스에서 파생되지만 여러 개의 인터페이스를 구현할 수 있습니다.

4. 인터페이스를 사용하여 "is a" 관계가 없을 수도 있는 클래스를 위한 특정 기능을 정의할 수 있습니다.

5. 예를 들어 System.IEquatable<T> 인터페이스는 클라이언트 코드에서 해당 형식의 같음에 대한 정의에 따라 두 개체가 같은지 여부를 결정할 수 있어야 하는 클래스 또는 구조체에서 구현할 수 있습니다.

6. IEquatable<T>은 기본 클래스 및 파생 클래스 사이에 존재하는 같은 종류의 "is a" 관계를 의미하는 것이 아닙니다(예: Mammal은 Animal).

7. 자세한 내용은 인터페이스(C# 프로그래밍 가이드)를 참조하십시오.


기본 클래스 멤버에 대한 파생 클래스 액세스

1. 파생 클래스는 기본 클래스의 public, protected, internal 및 protected internal 멤버에 액세스할 수 있습니다.

2. 파생 클래스는 기본 클래스의 private 멤버를 상속함에도 불구하고 해당 멤버에 액세스할 수 없습니다.

3. 하지만 모든 private 멤버는 파생 클래스에 존재하면서 기본 클래스에서 수행하는 작업과 동일한 작업을 수행할 수 있습니다.

4. 예를 들어 protected 기본 클래스 메서드가 private 필드에 액세스한다고 가정합니다.

5. 상속된 기본 클래스 메서드가 올바르게 작동하려면 파생 클래스에 해당 필드가 있어야 합니다.


추가 파생 방지

1. 클래스는 자신 또는 멤버를 sealed로 선언하여 다른 클래스가 상속하는 것을 막을 수 있습니다.

2. 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버(C# 프로그래밍 가이드)를 참조하십시오.


파생 클래스에서 기본 클래스 멤버 숨기기

1. 파생 클래스는 기본 클래스와 동일한 이름과 시그니처를 가진 멤버를 선언하여 기본 클래스 멤버를 숨길 수 있습니다.

2. new 한정자를 사용하면 멤버가 기본 클래스의 재정의가 아니라는 것을 명시적으로 나타낼 수 있습니다.

3. new를 반드시 사용할 필요는 없지만 new를 사용하지 않으면 컴파일러에서 경고를 생성합니다.

4. 자세한 내용은 Override 및 New 키워드를 사용하여 버전 관리(C# 프로그래밍 가이드) 및 Override 및 New 키워드를 사용해야 하는 경우(C# 프로그래밍 가이드)을 참조하십시오.




다형성


1. 다형성은 흔히 캡슐화와 상속의 뒤를 이어 개체 지향 프로그래밍의 세 번째 특징으로 일컬어집니다.

2. 다형성은 "여러 형태"를 의미하는 그리스어 단어이며 다음과 같은 두 가지 고유한 측면을 가집니다.

1) 런타임에 파생 클래스의 개체가 메서드 매개 변수 및 컬렉션 또는 배열과 같은 위치에서 기본 클래스의 개체로 처리될 수 있습니다. 이 경우 개체의 선언된 형식이 더 이상 해당 런타임 형식과 같지 않습니다.

2) 기본 클래스는 가상메서드를 정의 및 구현할 수 있으며, 파생 클래스는 이러한 가상 메서드를 재정의할 수 있습니다. 즉, 파생 클래스는 고유한 정의 및 구현을 제공합니다. 런타임에 클라이언트 코드에서 메서드를 호출하면 CLR은 개체의 런타임 형식을 조회하고 가상 메서드의 해당 재정의를 호출합니다. 따라서 소스 코드에서 기본 클래스에 대한 메서드를 호출하여 메서드의 파생 클래스 버전이 실행되도록 할 수 있습니다.

3. 가상 메서드를 사용하면 동일한 방식으로 관련 개체 그룹에 대한 작업을 수행할 수 있습니다.

4. 예를 들어, 사용자가 그리기 화면에서 다양한 종류의 도형을 만들 수 있는 그리기 응용 프로그램이 있다고 가정합니다.

5. 컴파일 타임에는 사용자가 어떤 특정 형식의 도형을 만들지 알 수 없습니다.

6. 그러나 응용 프로그램은 만들어지는 다양한 모든 형식의 도형을 추적해야 하며, 사용자의 마우스 작업에 따라 이러한 도형을 업데이트해야 합니다.

7. 다음과 같은 기본 두 단계로 다형성을 사용하여 이 문제를 해결할 수 있습니다.

1) 각 특정 도형 클래스가 공통 기본 클래스에서 파생되는 클래스 계층 구조를 만듭니다.

2) 가상 메서드를 사용하여 기본 클래스 메서드에 대한 단일 호출을 통해 모든 파생 클래스에 대해 적절한 메서드를 호출합니다.

8. 먼저, Shape라는 기본 클래스를 만들고 Rectangle, Circle 및 Triangle과 같은 파생 클래스를 만듭니다.

9. Shape 클래스에 Draw라는 가상 메서드를 제공하고, 각 파생 클래스에서 이를 재정의하여 클래스가 나타내는 특정 도형을 그립니다.

10. List<Shape> 개체를 만들고 이 개체에 Circle, Triangle 및 Rectangle을 추가합니다.

11. 그리기 화면을 업데이트하려면 foreach 루프를 사용하여 목록을 반복하고 목록의 각 Shape 개체에 대해 Draw 메서드를 호출합니다.

12. 목록의 각 개체에 선언된 형식 Shape가 있더라도 이는 호출될 런타임 형식(각 파생 클래스에 있는 메서드의 재정의된 버전)입니다.


public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Polymorphism at work #1: a Rectangle, Triangle and Circle
        // can all be used whereever a Shape is expected. No cast is
        // required because an implicit conversion exists from a derived 
        // class to its base class.
        System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();
        shapes.Add(new Rectangle());
        shapes.Add(new Triangle());
        shapes.Add(new Circle());

        // Polymorphism at work #2: the virtual method Draw is
        // invoked on each of the derived classes, not the base class.
        foreach (Shape s in shapes)
        {
            s.Draw();
        }

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

}

/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
 */


13. C#에서 모든 형식은 사용자 정의 형식을 포함한 모든 형식이 Object에서 파생되므로 다형 형식입니다.


다형성 개요


가상 멤버

1. 기본 클래스에서 파생 클래스가 상속되면 파생 클래스는 기본 클래스의 모든 메서드, 필드, 속성 및 이벤트를 얻습니다.

2. 파생 클래스의 디자이너는 다음의 여부를 선택할 수 있습니다.

1) 기본 클래스의 가상 멤버 재정의

2) 가장 가까운 기본 클래스 메서드를 재정의하지 않고 상속

3) 기본 클래스 구현을 숨기는 해당 멤버의 새 비가상 구현 정의

3. 파생 클래스는 기본 클래스 멤버가 virtual 또는 abstract로 선언된 경우에만 기본 클래스 멤버를 재정의할 수 있습니다.

4. 파생 멤버는 override 키워드를 사용하여 멤버가 가상 호출에 참여하도록 되어 있음을 명시적으로 나타내야 합니다.

5. 다음 코드는 예제를 제공합니다.


public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}


6. 필드는 가상일 수 없습니다.

7. 메서드, 속성, 이벤트 및 인덱서만 가상일 수 있습니다.

8. 파생 클래스가 가상 멤버를 재정의하면 해당 멤버는 해당 클래스의 인스턴스가 기본 클래스의 인스턴스로 액세스되는 경우에도 호출됩니다.

9. 다음 코드는 예제를 제공합니다.


DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Also calls the new method.


10. 가상 메서드 및 속성을 통해 파생 클래스는 메서드의 기본 클래스 구현을 사용할 필요 없이 기본 클래스를 확장할 수 있습니다.

11. 자세한 내용은 Override 및 New 키워드를 사용하여 버전 관리(C# 프로그래밍 가이드)을 참조하십시오.

12. 인터페이스는 구현이 파생 클래스에 남겨진 메서드 또는 메서드 집합을 정의하는 또 다른 방법을 제공합니다.

13. 자세한 내용은 인터페이스(C# 프로그래밍 가이드)을 참조하십시오.


새 멤버로 기본 클래스 멤버 숨기기

14. 파생 멤버가 기본 클래스의 멤버와 동일한 이름을 갖되 가상 호출에는 참여하지 않도록 하려면 new 키워드를 사용하면 됩니다.

15. new 키워드는 바꿀 클래스 멤버의 반환 형식 앞에 배치됩니다.

16. 다음 코드는 예제를 제공합니다.


public class BaseClass

{
    public void DoWork() { WorkField++; }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { WorkField++; }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}


17. 숨겨진 기본 클래스 멤버는 파생 클래스의 인스턴스를 기본 클래스의 인스턴스로 캐스팅하여 클라이언트 코드에서 계속 액세스 할 수 있습니다.


DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.


파생 클래스가 가상 멤버를 재정의하지 못하도록 설정

1. 가상 멤버는 가상 멤버와 원래 가상 멤버를 선언한 클래스 간에 선언된 클래스 수와 관계없이 무기한 가상으로 유지됩니다.

2. 클래스 A가 가상 멤버를 선언하고, 클래스 B가 A에서 파생되며, 클래스 C가 B에서 파생되면 클래스 C는 클래스 B가 해당 멤버에 대한 재정의를 선언했는지 여부와 관계없이 가상 멤버를 상속하며, 가상 멤버를 재정의할 수 있습니다.

3. 다음 코드는 예제를 제공합니다.


public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}


4. 파생 클래스는 재정의를 sealed로 선언하여 가상 상속을 중지할 수 있습니다.

5. 이렇게 하려면 클래스 멤버 선언에서 override 키워드 앞에 sealed 키워드를 배치해야 합니다.

6. 다음 코드는 예제를 제공합니다.


public class C : B
{
    public sealed override void DoWork() { }
}


7. 앞의 예제에서 DoWork 메서드는 C에서 파생된 모든 클래스에 대해 더 이상 가상이 아닙니다.

8. 그러나 이 메서드는 C의 인스턴스가 형식 B 또는 형식 A로 캐스팅되더라도 여전히 C의 인스턴스에 대해서는 가상입니다.

9. sealed 메서드는 다음 예제에 표시된 것처럼 new 키워드를 사용하여 파생 클래스로 바꿀 수 있습니다.


public class D : C
{
    public new void DoWork() { }
}


10. 이 경우 형식 D 변수를 사용하여 D에 대해 DoWork을 호출하면 새 DoWork가 호출됩니다.

11. 형식 C, B 또는 A 변수를 사용하여 D의 인스턴스에 액세스하면 DoWork에 대한 호출에서 가상 상속의 규칙을 따라 해당 호출을 클래스 C에 대한 DoWork의 구현으로 라우팅합니다.


파생 클래스에서 기본 클래스 가상 멤버 액세스

1. 메서드 또는 속성을 바꾸었거나 재정의한 파생 클래스는 계속해서 base 키워드를 사용하여 기본 클래스에 대한 메서드 또는 속성에 액세스할 수 있습니다.

2. 다음 코드는 예제를 제공합니다.


public class Base
{
    public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
    public override void DoWork()
    {
        //Perform Derived's work here
        //...
        // Call DoWork on base class
        base.DoWork();
    }
}



참고

1. 가상 멤버는 base를 사용하여 자체 구현에서 해당 멤버의 기본 클래스 구현을 호출하는 것이 좋습니다.

2. 기본 클래스 동작이 발생하도록 하면 파생 클래스가 파생 클래스에 대한 동작 구현에 집중할 수 있습니다.

3. 기본 클래스 구현이 호출되지 않는 경우 해당 동작이 기본 클래스의 동작과 호환되도록 하는 것은 파생 클래스의 책임입니다.



Override 및 New 키워드를 사용하여 버전 관리


1. 서로 다른 라이브러리 내의 기본 클래스와 파생 클래스 간에 버전 관리 기능을 향상시키며 이전 버전과 호환될 수 있도록 C# 언어가 설계되었습니다.

2. 예를 들어, C#에서는 기본 클래스에 새 멤버를 추가하고 파생 클래스의 멤버와 동일한 이름을 부여해도 오류나 예기치 않은 동작이 발생하지 않습니다.

3. 또한 클래스에서 메서드가 상속된 메서드를 무시하도록 의도되었는지 또는 메서드가 유사하게 명명된 상속된 메서드를 숨기는 새 메서드인지를 명시적으로 표시해야 합니다.

4. C#에서는 파생 클래스에 기본 클래스 메서드와 이름이 동일한 메서드를 포함할 수 있습니다.

1) 기본 클래스 메서드는 virtual로 정의해야 합니다.

2) 파생 클래스의 메서드 앞에 new 또는 override 키워드를 사용하지 않으면 컴파일러에서 경고가 발생하고 메서드에 new 키워드가 있는 것처럼 작동합니다.

3) 파생 클래스의 메서드 앞에 new 키워드를 사용하면 메서드가 기본 클래스의 메서드에 종속되지 않은 것으로 정의됩니다.

4) 파생 클래스의 메서드 앞에 override 키워드를 사용하면 파생 클래스의 개체에서 기본 클래스 메서드 대신 이 메서드를 호출합니다.

5) 기본 클래스 메서드는 base 키워드를 사용하여 파생 클래스 내에서 호출할 수 있습니다.

6) override, virtual 및 new 키워드는 속성, 인덱서 및 이벤트에도 적용할 수 있습니다.

5. C# 메서드는 기본적으로 비가상 메서드입니다.

6. 메서드를 가상 메서드로 선언하면 이 메서드를 상속하는 클래스에서 자체 버전을 구현할 수 있습니다.

7. 메서드를 가상 메서드로 만들려면 기본 클래스의 메서드 선언에 virtual 한정자를 사용해야 합니다.

8. 그러면 파생 클래스가 override 키워드를 사용하여 기본 가상 메서드를 재정의하거나 new 키워드를 사용하여 기본 클래스의 가상 메서드를 숨길 수 있습니다.

9. override 키워드나 new 키워드를 모두 지정하지 않으면 컴파일러에서 경고가 발생하고 파생 클래스의 메서드가 기본 클래스의 메서드를 숨기게 됩니다.

10. 이러한 경우를 구체적으로 살펴보기 위하여 A라는 회사에서 프로그램에 사용되는 GraphicsClass라는 클래스를 만든 경우를 가정해 보겠습니다. GraphicsClass는 다음과 같습니다.


class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
}


11. 회사에서는 이 클래스를 사용하며, 개발자는 이 클래스로 고유한 클래스를 파생시켜 새 메서드를 추가합니다.


class YourDerivedGraphicsClass : GraphicsClass
{
    public void DrawRectangle() { }
}


12. 회사 A에서 다음 코드와 같은 새 버전의 GraphicsClass를 릴리스하기 전까지는 응용 프로그램을 아무런 문제 없이 사용할 수 있습니다.


class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
    public virtual void DrawRectangle() { }
}


13. 새 버전의 GraphicsClass에는 DrawRectangle이라는 메서드가 들어 있습니다.

14. 처음에는 아무런 일도 일어나지 않습니다.

15. 새 버전은 이전 버전과 이진 호환됩니다.

16. 새 클래스가 컴퓨터 시스템에 설치된 경우에도 배포한 소프트웨어는 계속 작동합니다.

17. 메서드 DrawRectangle에 대한 기존의 모든 호출은 파생 클래스에서 기존 버전을 계속하여 참조합니다.

18. 그러나 새 버전의 GraphicsClass를 사용하여 응용 프로그램을 다시 컴파일하면 컴파일러에서 CS0108 경고가 발생합니다.

19. 이 경고는 응용 프로그램에서 DrawRectangle 메서드가 동작하는 방식을 고려해야 한다는 사실을 알립니다.

20. 사용자의 메서드로 새 기본 클래스 메서드를 재정의하려면 override 키워드를 사용합니다.


class YourDerivedGraphicsClass : GraphicsClass
{
    public override void DrawRectangle() { }
}


21. override 키워드를 사용하면 YourDerivedGraphicsClass에서 파생된 모든 개체에 파생 클래스 버전의 DrawRectangle이 사용됩니다.

22. YourDerivedGraphicsClass에서 파생된 개체는 base 키워드를 사용하여 기본 클래스 버전의 DrawRectangle에 액세스할 수 있습니다.


base.DrawRectangle();


23. 사용자의 메서드로 새 기본 클래스 메서드를 재정의하지 않으려면 다음 사항을 고려해야 합니다.

24. 두 메서드를 혼동하지 않도록 메서드의 이름을 변경할 수 있습니다.

25. 이렇게 하는 데는 많은 시간이 걸리고 오류가 발생할 수 있으며 상황에 따라서는 불가능한 작업일 수도 있습니다.

26. 그러나 프로젝트가 비교적 작은 경우에는 Visual Studio의 리팩터링 옵션을 사용하여 메서드의 이름을 바꿀 수 있습니다.

27. 자세한 내용은 Refactoring Classes and Types (Class Designer)을 참조하십시오.

28. 또는 파생 클래스 정의에 new 키워드를 사용하여 경고를 방지할 수 있습니다.


class YourDerivedGraphicsClass : GraphicsClass
{
    public new void DrawRectangle() { }
}


29. new 키워드를 사용하는 것은 사용자의 정의가 기본 클래스에 포함된 정의를 숨긴다는 점을 컴파일러에 알리는 것입니다.

30. 이것은 기본적인 동작입니다.


재정의 및 메서드 선택

1. 클래스에서 메서드 이름을 지정한 경우, 이름이 동일한 메서드가 두 개이고 전달된 매개 변수와 호환되는 매개 변수가 있는 경우와 같이 호출할 수 있는 메서드가 여러 개 있으면 C# 컴파일러에서 가장 적합한 메서드를 선택합니다.

2. 다음은 호환될 수 있는 메서드입니다.


public class Derived : Base
{
    public override void DoWork(int param) { }
    public void DoWork(double param) { }
}


3. Derived의 인스턴스에 대한 DoWork를 호출할 때 C# 컴파일러는 우선 Derived에 원래 선언된 버전의 DoWork와 호환되는 호출을 시도합니다.

4. 재정의 메서드는 클래스에서 선언된 것으로 간주되지 않으며 기본 클래스에서 선언된 메서드의 새 구현으로 간주됩니다.

5. C# 컴파일러가 Derived의 원래 메서드에 메서드 호출을 일치시킬 수 없는 경우에만 동일한 이름과 호환되는 매개 변수를 사용하여 재정의된 메서드에 호출을 일치시키려 시도합니다.

6. 예를 들면 다음과 같습니다.


int val = 5;
Derived d = new Derived();
d.DoWork(val);  // Calls DoWork(double).


7. 변수 val은 double로 암시적으로 변환할 수 있으므로 C# 컴파일러에서는 DoWork(double)를 DoWork(int) 대신 호출합니다.

8. 이를 방지하는 데는 두 가지 방법이 있습니다.

9. 첫 번째 방법은 동일한 이름의 새 메서드를 가상 메서드로 선언하지 않는 것입니다.

10. 두 번째 방법으로는 Derived의 인스턴스를 Base로 캐스팅하여 기본 클래스 메서드 목록을 검색하여 가상 메서드를 호출하도록 C# 컴파일러에 지시할 수 있습니다.

11. 이 메서드는 가상이므로 Derived에 대한 DoWork(int)의 구현이 호출됩니다.

12. 예를 들면 다음과 같습니다.


((Base)d).DoWork(val);  // Calls DoWork(int) on Derived.


13. new 및 overrided의 더 많은 예제는 Override 및 New 키워드를 사용해야 하는 경우(C# 프로그래밍 가이드)를 참조하십시오.




Override 및 New 키워드를 사용해야 하는 경우


1. C#에서 파생 클래스의 메서드는 기본 클래스의 메서드와 같은 이름을 가질 수 있습니다.

2. new 및 override 키워드를 사용하여 메서드가 상호 작용하는 방법을 지정할 수 있습니다.

3. override 한정자는 기본 클래스 메서드를 확장하며, new 한정자는 그것을 숨깁니다. 차이는 이 항목의 예제에 설명되어 있습니다.

4. 콘솔 응용 프로그램에서 BaseClass 및 DerivedClass, 두 클래스를 선언합니다. DerivedClass는 BaseClass에서 상속됩니다.


class BaseClass
{
    public void Method1()
    {
        Console.WriteLine("Base - Method1");
    }
}

class DerivedClass : BaseClass
{
    public void Method2()
    {
        Console.WriteLine("Derived - Method2");
    }
}


5. Main 메서드에서 bc, dc 및 bcdc 변수를 선언합니다.

1) bc가 BaseClass 형식이고 그 값이 BaseClass인 경우

2) dc가 DerivedClass 형식이고 그 값이 DerivedClass인 경우

3) bcdc가 BaseClass 형식이고 그 값이 DerivedClass인 경우 이것은 주목해야 하는 변수입니다.

6. bc 및 bcdc에 BaseClass 형식이 있으므로 캐스팅을 사용하지 않는 한 Method1에만 직접 액세스할 수 있습니다. 7. dc 변수는 Method1 및 Method2에 액세스할 수 있습니다. 이러한 관계는 다음 코드에서 볼 수 있습니다.


class Program
{
    static void Main(string[] args)
    {
        BaseClass bc = new BaseClass();
        DerivedClass dc = new DerivedClass();
        BaseClass bcdc = new DerivedClass();

        bc.Method1();
        dc.Method1();
        dc.Method2();
        bcdc.Method1();
    }
    // Output:
    // Base - Method1
    // Base - Method1
    // Derived - Method2
    // Base - Method1
}


8. 그런 후에, 다음 Method2 메서드를 BaseClass에 추가합니다.

9. 이 메서드의 서명이 DerivedClass의 Method2 메서드의 서명과 일치합니다.


public void Method2()
{
    Console.WriteLine("Base - Method2");
}


10. BaseClass에는 이제 Method2 메서드가 있기 때문에 두 번째 호출 문은 다음 코드에 표시된 것처럼 BaseClass 변수 bc 및 bcdc에 추가할 수 있습니다.


bc.Method1();
bc.Method2();
dc.Method1();
dc.Method2();
bcdc.Method1();
bcdc.Method2();


11. 프로젝트를 빌드할 때, BaseClass에 Method2 메서드를 추가하면 경고가 발생하는 것을 확인하게 됩니다.

12. 경고에 DerivedClass의 Method2 메서드가 BaseClass의 Method2 메서드를 숨긴다고 나와 있습니다.

13. 그러한 결과를 얻으려면 Method2 정의에서 new 키워드를 사용하는 것이 좋습니다.

14. 또한 Method2 메서드 중 하나를 이름 변경하여 경고를 해결하지만 항상 실용적인 것은 아닙니다.

15. new를 추가하기 전에 추가 호출 문에서 생성되는 출력을 볼 수 있는 프로그램을 실행합니다.

16. 다음과 같은 결과가 표시됩니다.


// Output:
// Base - Method1
// Base - Method2
// Base - Method1
// Derived - Method2
// Base - Method1
// Base - Method2


17. new 키워드는 해당 출력을 만드는 관계를 유지하지만 경고를 억제합니다.

18. BaseClass 형식의 변수는 BaseClass의 멤버에 지속적으로 액세스하며 DerivedClass 형식의 변수는 우선 DerivedClass의 멤버에 액세스한 후 BaseClass에서 상속된 멤버를 고려합니다.

19. 경고를 표시하지 않으려면 다음 코드에 표시된 대로 new 한정자를 DerivedClass에서 Method2의 정의로 추가합니다.

20. public 앞 또는 뒤에 한정자를 추가할 수 있습니다.


public new void Method2()
{
    Console.WriteLine("Derived - Method2");
}


21. 출력이 변경되지 않은 것을 확인하기 위해 프로그램을 다시 실행합니다.

22. 경고가 더 이상 나타나는지 않는지도 확인합니다.

23. new를 사용함으로써 수정하는 구성원이 기본 클래스에서 상속된 구성원을 숨기는 것을 인식하고 있음을 어설션하는 것입니다.

24. 상속을 통한 이름 숨기기에 대한 자세한 내용은 new 한정자(C# 참조)를 참조하십시오.

25.이 동작을 override를 사용하는 경우의 효과와 비교하려면 다음 메서드를 DerivedClass에 추가합니다.

26. public 전에 또는 후에 override 한정자를 추가할 수 있습니다.


public override void Method1()
{
    Console.WriteLine("Derived - Method1");
}


27. BaseClass의 Method1 정의에 virtual 한정자를 추가합니다. public 전에 또는 후에 virtual 한정자를 추가할 수 있습니다.


public virtual void Method1()
{
    Console.WriteLine("Base - Method1");
}


28. 프로젝트를 다시 실행합니다. 특히 다음 출력의 마지막 두 줄을 주목하십시오.


// Output:
// Base - Method1
// Base - Method2
// Derived - Method1
// Derived - Method2
// Derived - Method1
// Base - Method2


29. override 한정자는 bcdc를 통해 DerivedClass에 정의되어 있는 Method1 메서드에 액세스할 수 있습니다.

30. 일반적으로 상속 계층 구조에서 원하는 동작입니다.

31. 파생 클래스에서 정의되는 메서드를 사용하도록 파생 클래스에서 생성되는 값을 가진 개체를 사용합니다.

32. override를 사용하여 기본 클래스 메서드를 확장하는 동작을 달성합니다.

33. 다음 코드에는 전체 예제가 포함되어 있습니다.


using System;
using System.Text;

namespace OverrideAndNew
{
    class Program
    {
        static void Main(string[] args)
        {
            BaseClass bc = new BaseClass();
            DerivedClass dc = new DerivedClass();
            BaseClass bcdc = new DerivedClass();

            // The following two calls do what you would expect. They call
            // the methods that are defined in BaseClass.
            bc.Method1();
            bc.Method2();
            // Output:
            // Base - Method1
            // Base - Method2


            // The following two calls do what you would expect. They call
            // the methods that are defined in DerivedClass.
            dc.Method1();
            dc.Method2();
            // Output:
            // Derived - Method1
            // Derived - Method2


            // The following two calls produce different results, depending 
            // on whether override (Method1) or new (Method2) is used.
            bcdc.Method1();
            bcdc.Method2();
            // Output:
            // Derived - Method1
            // Base - Method2
        }
    }

    class BaseClass
    {
        public virtual void Method1()
        {
            Console.WriteLine("Base - Method1");
        }

        public virtual void Method2()
        {
            Console.WriteLine("Base - Method2");
        }
    }

    class DerivedClass : BaseClass
    {
        public override void Method1()
        {
            Console.WriteLine("Derived - Method1");
        }

        public new void Method2()
        {
            Console.WriteLine("Derived - Method2");
        }
    }
}


34. 다음 예제는 다른 컨텍스트에서의 비슷한 동작을 보여 줍니다.

35. 다음 예제에서는 Car라는 기본 클래스 하나와 ConvertibleCar 및 Minivan에서 파생되는 두 개의 클래스를 정의합니다.

36. 기본 클래스에는 DescribeCar 메서드가 포함되어 있습니다.

37. 메서드가 자동차에 대한 기본적 설명을 표시한 다음 ShowDetails를 호출하여 추가 정보를 제공합니다.

38. 각 클래스 3개는 ShowDetails 메서드를 정의합니다.

39. ConvertibleCar 클래스에 ShowDetails을 정의하는 데 new 한정자가 사용되었습니다.

40. Minivan 클래스에 ShowDetails을 정의하는 데 override 한정자가 사용되었습니다.


// Define the base class, Car. The class defines two methods,
// DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each derived
// class also defines a ShowDetails method. The example tests which version of
// ShowDetails is selected, the base class method or the derived class method.
class Car
{
    public void DescribeCar()
    {
        System.Console.WriteLine("Four wheels and an engine.");
        ShowDetails();
    }

    public virtual void ShowDetails()
    {
        System.Console.WriteLine("Standard transportation.");
    }
}

// Define the derived classes.

// Class ConvertibleCar uses the new modifier to acknowledge that ShowDetails
// hides the base class method.
class ConvertibleCar : Car
{
    public new void ShowDetails()
    {
        System.Console.WriteLine("A roof that opens up.");
    }
}

// Class Minivan uses the override modifier to specify that ShowDetails
// extends the base class method.
class Minivan : Car
{
    public override void ShowDetails()
    {
        System.Console.WriteLine("Carries seven people.");
    }
}


41. 이 예제는 어느 버전의 ShowDetails가 호출되었는지를 테스트합니다. 다음 메서드 TestCars1은 각 클래스의 인스턴스를 선언한 다음 각 인스턴스에 DescribeCar를 호출합니다.


public static void TestCars1()
{
    System.Console.WriteLine("\nTestCars1");
    System.Console.WriteLine("----------");

    Car car1 = new Car();
    car1.DescribeCar();
    System.Console.WriteLine("----------");


    // Notice the output from this test case. The new modifier is
    // used in the definition of ShowDetails in the ConvertibleCar
    // class.  

    ConvertibleCar car2 = new ConvertibleCar();
    car2.DescribeCar();
    System.Console.WriteLine("----------");

    Minivan car3 = new Minivan();
    car3.DescribeCar();
    System.Console.WriteLine("----------");
}


42. TestCars1은 다음과 같은 출력을 생성합니다.

43. 특히 car2에 대해 예상과 다르게 나타난 결과에 주목하십시오.

44. 개체의 형식은 ConvertibleCar이나 DescribeCar는 해당 메서드가 override 한정자가 아닌 new 한정자와 함께 선언되므로 ConvertibleCar 클래스에 정의되어 있는 ShowDetails 버전에 액세스할 수 없습니다.

45. 그 결과 ConvertibleCar 개체에는 Car 개체와 동일한 설명이 표시됩니다.

46. Minivan 개체인 car3의 결과와 반대되는 개념입니다.

47. 이 경우 Minivan 클래스에 선언된 ShowDetails 메서드는 Car 클래스에 선언된 ShowDetails 메서드를 재정의하고 표시되는 설명에서는 minivan을 설명합니다.


// TestCars1
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------


48. TestCars2는 Car 형식을 가진 개체 목록을 만듭니다.

49. 개체의 값은 Car, ConvertibleCar 및 Minivan 클래스에서 인스턴스화됩니다.

50. DescribeCar는 목록의 각 요소에서 호출됩니다.

51. 다음 코드에서는 TestCars2의 정의를 보여 줍니다.


public static void TestCars2()
{
    System.Console.WriteLine("\nTestCars2");
    System.Console.WriteLine("----------");

    var cars = new List<Car> { new Car(), new ConvertibleCar(), 
        new Minivan() };

    foreach (var car in cars)
    {
        car.DescribeCar();
        System.Console.WriteLine("----------");
    }
}


52. 다음과 같은 출력이 표시됩니다.

53. 이는 TestCars1에서 표시한 결과와 동일하다는 점에 주목하십시오.

54. ConvertibleCar 클래스의 ShowDetails 메서드는 개체 형식이 TestCars1에서와 같이 ConvertibleCar인지, TestCars2에서와 같이 Car인지 여부에 관계 없이 호출되지 않습니다.

55. 이와는 반대로, car3는 형식이 Minivan 또는 Car인지에 관계 없이 두 경우 모두 Minivan 클래스에서 ShowDetails 메서드를 호출합니다.


// TestCars2
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------


56. TestCars3 및 TestCars4 메서드로 예제를 완료합니다.

57. 이러한 메서드는 먼저 ConvertibleCar 및 Minivan(TestCars3) 형식을 가지도록 선언되는 개체에서 시작하여 Car(TestCars4) 형식을 가지도록 선언되는 개체로 ShowDetails를 직접 호출합니다.

58. 다음 코드는 이 두 메서드를 정의합니다.


public static void TestCars3()
{
    System.Console.WriteLine("\nTestCars3");
    System.Console.WriteLine("----------");
    ConvertibleCar car2 = new ConvertibleCar();
    Minivan car3 = new Minivan();
    car2.ShowDetails();
    car3.ShowDetails();
}

public static void TestCars4()
{
    System.Console.WriteLine("\nTestCars4");
    System.Console.WriteLine("----------");
    Car car2 = new ConvertibleCar();
    Car car3 = new Minivan();
    car2.ShowDetails();
    car3.ShowDetails();
}


59. 메서드에서 이 항목의 첫 번째 예제의 결과에 해당하는 출력을 생성합니다.


// TestCars3
// ----------
// A roof that opens up.
// Carries seven people.

// TestCars4
// ----------
// Standard transportation.
// Carries seven people.


60. 다음 코드에서는 완전한 프로젝트와 그 출력을 보여 줍니다.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OverrideAndNew2
{
    class Program
    {
        static void Main(string[] args)
        {
            // Declare objects of the derived classes and test which version
            // of ShowDetails is run, base or derived.
            TestCars1();

            // Declare objects of the base class, instantiated with the
            // derived classes, and repeat the tests.
            TestCars2();

            // Declare objects of the derived classes and call ShowDetails
            // directly.
            TestCars3();

            // Declare objects of the base class, instantiated with the
            // derived classes, and repeat the tests.
            TestCars4();
        }

        public static void TestCars1()
        {
            System.Console.WriteLine("\nTestCars1");
            System.Console.WriteLine("----------");

            Car car1 = new Car();
            car1.DescribeCar();
            System.Console.WriteLine("----------");

            // Notice the output from this test case. The new modifier is
            // used in the definition of ShowDetails in the ConvertibleCar
            // class.  
            ConvertibleCar car2 = new ConvertibleCar();
            car2.DescribeCar();
            System.Console.WriteLine("----------");

            Minivan car3 = new Minivan();
            car3.DescribeCar();
            System.Console.WriteLine("----------");
        }
        // Output:
        // TestCars1
        // ----------
        // Four wheels and an engine.
        // Standard transportation.
        // ----------
        // Four wheels and an engine.
        // Standard transportation.
        // ----------
        // Four wheels and an engine.
        // Carries seven people.
        // ----------


        public static void TestCars2()
        {
            System.Console.WriteLine("\nTestCars2");
            System.Console.WriteLine("----------");

            var cars = new List<Car> { new Car(), new ConvertibleCar(), 
                new Minivan() };

            foreach (var car in cars)
            {
                car.DescribeCar();
                System.Console.WriteLine("----------");
            }
        }
        // Output:
        // TestCars2
        // ----------
        // Four wheels and an engine.
        // Standard transportation.
        // ----------
        // Four wheels and an engine.
        // Standard transportation.
        // ----------
        // Four wheels and an engine.
        // Carries seven people.
        // ----------


        public static void TestCars3()
        {
            System.Console.WriteLine("\nTestCars3");
            System.Console.WriteLine("----------");
            ConvertibleCar car2 = new ConvertibleCar();
            Minivan car3 = new Minivan();
            car2.ShowDetails();
            car3.ShowDetails();
        }
        // Output:
        // TestCars3
        // ----------
        // A roof that opens up.
        // Carries seven people.


        public static void TestCars4()
        {
            System.Console.WriteLine("\nTestCars4");
            System.Console.WriteLine("----------");
            Car car2 = new ConvertibleCar();
            Car car3 = new Minivan();
            car2.ShowDetails();
            car3.ShowDetails();
        }
        // Output:
        // TestCars4
        // ----------
        // Standard transportation.
        // Carries seven people.
    }


    // Define the base class, Car. The class defines two virtual methods,
    // DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each derived
    // class also defines a ShowDetails method. The example tests which version of
    // ShowDetails is used, the base class method or the derived class method.
    class Car
    {
        public virtual void DescribeCar()
        {
            System.Console.WriteLine("Four wheels and an engine.");
            ShowDetails();
        }

        public virtual void ShowDetails()
        {
            System.Console.WriteLine("Standard transportation.");
        }
    }


    // Define the derived classes.

    // Class ConvertibleCar uses the new modifier to acknowledge that ShowDetails
    // hides the base class method.
    class ConvertibleCar : Car
    {
        public new void ShowDetails()
        {
            System.Console.WriteLine("A roof that opens up.");
        }
    }

    // Class Minivan uses the override modifier to specify that ShowDetails
    // extends the base class method.
    class Minivan : Car
    {
        public override void ShowDetails()
        {
            System.Console.WriteLine("Carries seven people.");
        }
    }

}




방법: ToString 메서드 재정의


1. C#의 모든 클래스나 구조체는 Object 클래스를 암시적으로 상속합니다.

2. 따라서 C#의 모든 개체는 해당 개체에 대한 문자열 표현을 반환하는 ToString 메서드를 가집니다.

3. 예를 들어, int 형식의 모든 변수에는 그 내용을 문자열로 반환하는 데 사용할 수 있는 ToString 메서드가 있습니다.

int x = 42;
string strx = x.ToString();
Console.WriteLine(strx);
// Output:
// 42


4. 사용자 지정 클래스나 구조체를 만들 때 클라이언트 코드에 해당 형식에 대한 정보를 제공하려면 ToString 메서드를 재정의해야 합니다.

5. 형식 문자열의 함께 사용자 지정 서식을 사용 하는 방법에 대 한 정보는 ToString메서드를 참조 하십시오 .NET Framework의 형식 서식 지정.


클래스나 구조체에서 ToString 메서드를 재정의하려면


1. 다음 한정자와 반환 형식을 사용하여 ToString 메서드를 선언합니다.


public override string ToString(){}


2. 문자열이 반환되도록 메서드를 구현합니다.

3. 다음 예제에서는 클래스의 이름뿐 아니라 클래스의 특정 인스턴스와 관련된 데이터도 반환합니다.


class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return "Person: " + Name + " " + Age;
    }
}


4. 다음 코드 예제와 같이 ToString 메서드를 테스트할 수 있습니다.


Person person = new Person { Name = "John", Age = 12 };
Console.WriteLine(person);
// Output:
// Person: John 12




추상 및 봉인 클래스와 클래스 멤버


1. abstract 키워드를 사용하면 불완전하여 파생 클래스에서 구현해야 하는 클래스 및 클래스 멤버를 만들 수 있습니다.

2. sealed 키워드를 사용하면 이전에 virtual로 표시되었던 클래스나 특정 클래스 멤버가 상속되지 못하도록 할 수 있습니다.


추상 클래스 및 클래스 멤버

1. 클래스 정의 앞에 abstract 키워드를 배치하여 클래스를 추상으로 선언할 수 있습니다.

2. 예를 들면 다음과 같습니다.


public abstract class A
{
    // Class members here.
}


3. 추상 클래스는 인스턴스화할 수 없습니다.

4. 추상 클래스의 목적은 여러 파생 클래스에서 공유할 수 있는 기본 클래스의 공통적인 정의를 제공하는 것입니다.

5. 예를 들어 클래스 라이브러리에서 여러 자체 함수에 매개 변수로 사용되는 추상 클래스를 정의한 다음 해당 라이브러리를 사용하는 프로그래머가 파생 클래스를 만들어 클래스의 고유 구현을 제공하도록 할 수 있습니다.

6. 추상 클래스에서는 추상 메서드도 정의할 수 있습니다.

7. 메서드의 반환 형식 앞에 abstract 키워드를 추가하면 추상 메서드가 정의됩니다.

8. 예를 들면 다음과 같습니다.


public abstract class A
{
    public abstract void DoWork(int i);
}


9. 추상 메서드에는 구현이 없으므로 메서드 정의 다음에는 일반적인 메서드 블록 대신 세미콜론이 옵니다.

10. 추상 클래스의 파생 클래스에서는 모든 추상 메서드를 구현해야 합니다.

11. 추상 클래스에서 기본 클래스의 가상 메서드를 상속하는 경우 추상 클래스에서는 추상 메서드를 사용하여 가상 메서드를 재정의할 수 있습니다.

12. 예를 들면 다음과 같습니다.


// compile with: /target:library
public class D
{
    public virtual void DoWork(int i)
    {
        // Original implementation.
    }
}

public abstract class E : D
{
    public abstract override void DoWork(int i);
}

public class F : E
{
    public override void DoWork(int i)
    {
        // New implementation.
    }
}


13. virtual 메서드는 abstract로 선언되어도 추상 클래스에서 상속된 모든 클래스에 대해 여전히 가상입니다.

14. 추상 메서드를 상속하는 클래스에서는 메서드의 원본 구현에 액세스할 수 없습니다.

15. 앞의 예제에서 F 클래스의 DoWork에서는 D 클래스의 DoWork를 호출할 수 없습니다.

16. 따라서 추상 클래스는 파생 클래스에서 가상 메서드에 대한 새 메서드 구현을 반드시 제공하도록 제한할 수 있습니다.


봉인 클래스 및 클래스 멤버

1. 클래스 정의 앞에 sealed 키워드를 배치하여 클래스를 sealed로 선언할 수 있습니다.

2. 예를 들면 다음과 같습니다.


public sealed class D
{
    // Class members here.
}


3. 봉인 클래스는 기본 클래스로 사용할 수 없습니다.

4. 그러므로 추상 클래스가 될 수도 없습니다.

5. 봉인 클래스는 상속할 수 없습니다.

6. 봉인 클래스는 기본 클래스로 사용될 수 없으므로 일부 런타임 최적화에서는 봉인 클래스 멤버 호출이 약간 더 빨라집니다.

7. 기본 클래스의 가상 멤버를 재정의하는 파생 클래스의 메서드, 인덱서, 속성 또는 이벤트는 해당 멤버를 봉인으로 선언할 수 있습니다.

8. 이렇게 하면 이후에 파생되는 클래스에서는 해당 멤버가 가상이 아니게 됩니다.

9. 클래스 멤버 선언에서 override 키워드 앞에 sealed 키워드를 넣으면 멤버가 봉인으로 선언됩니다.

10. 예를 들면 다음과 같습니다.


public class D : C
{
    public sealed override void DoWork() { }
}




방법: 추상 속성 정의


1. 다음 예제에서는 추상 속성을 정의하는 방법을 보여 줍니다.

2. 추상 속성 선언에서는 속성 접근자에 대한 구현을 제공하지 않습니다.

3. 여기서는 클래스가 속성을 지원하도록 선언하지만 접근자 구현은 파생 클래스의 몫으로 남겨 둡니다.

4. 다음 예제에서는 기본 클래스에서 상속된 추상 속성을 구현하는 방법을 보여 줍니다.

5. 이 샘플은 파일 세 개로 구성되어 있습니다.

6. 각 파일은 개별적으로 컴파일되고 그 결과 어셈블리는 다음 컴파일에서 참조합니다.

1) abstractshape.cs: 추상 Area 속성이 들어 있는 Shape 클래스입니다.

2) shapes.cs: Shape 클래스의 서브클래스입니다.

3) shapetest.cs: Shape 파생 개체 일부의 영역을 표시하기 위한 테스트 프로그램입니다.

7. 예제를 컴파일하려면 다음 명령을 사용해야 합니다.

csc abstractshape.cs shapes.cs shapetest.cs

8. 이렇게 하면 실행 파일 shapetest.exe가 만들어집니다.


예제

1. 이 파일에서는 double 형식의 Area 속성을 포함하는 Shape 클래스를 선언합니다.


// compile with: csc /target:library abstractshape.cs
public abstract class Shape
{
    private string name;

    public Shape(string s)
    {
        // calling the set accessor of the Id property.
        Id = s;
    }

    public string Id
    {
        get
        {
            return name;
        }

        set
        {
            name = value;
        }
    }

    // Area is a read-only property - only a get accessor is needed:
    public abstract double Area
    {
        get;
    }

    public override string ToString()
    {
        return Id + " Area = " + string.Format("{0:F2}", Area);
    }
}


2, 속성에 대한 한정자는 속성 선언 자체에 배치됩니다. 예를 들면 다음과 같습니다.


public abstract double Area


3. 이 예제에서의 Area와 같이 추상 속성을 선언할 때 단순히 사용할 수 있는 속성 접근자를 나타내고 구현하지는 않습니다.

4. 이 예제에서는 get 접근자만 사용할 수 있으므로 속성이 읽기 전용입니다.


예제

1. 다음 코드에서는 Shape의 서브클래스 세 개를 보여 주고 이 서브클래스에서 Area 속성을 재정의하여 자체 구현을 제공하는 방법을 보여 줍니다.


// compile with: csc /target:library /reference:abstractshape.dll shapes.cs
public class Square : Shape
{
    private int side;

    public Square(int side, string id)
        : base(id)
    {
        this.side = side;
    }

    public override double Area
    {
        get
        {
            // Given the side, return the area of a square:
            return side * side;
        }
    }
}

public class Circle : Shape
{
    private int radius;

    public Circle(int radius, string id)
        : base(id)
    {
        this.radius = radius;
    }

    public override double Area
    {
        get
        {
            // Given the radius, return the area of a circle:
            return radius * radius * System.Math.PI;
        }
    }
}

public class Rectangle : Shape
{
    private int width;
    private int height;

    public Rectangle(int width, int height, string id)
        : base(id)
    {
        this.width = width;
        this.height = height;
    }

    public override double Area
    {
        get
        {
            // Given the width and height, return the area of a rectangle:
            return width * height;
        }
    }
}


예제

1. 다음은 여러 Shape 파생 개체를 만들고 해당 면적을 출력하는 테스트 프로그램 코드입니다.


// compile with: csc /reference:abstractshape.dll;shapes.dll shapetest.cs
class TestClass
{
    static void Main()
    {
        Shape[] shapes =
        {
            new Square(5, "Square #1"),
            new Circle(3, "Circle #1"),
            new Rectangle( 4, 5, "Rectangle #1")
        };

        System.Console.WriteLine("Shapes Collection");
        foreach (Shape s in shapes)
        {
            System.Console.WriteLine(s);
        }
    }
}
/* Output:
    Shapes Collection
    Square #1 Area = 25.00
    Circle #1 Area = 28.27
    Rectangle #1 Area = 20.00
*/




정적 클래스 및 정적 클래스 멤버


1. static 클래스는 기본적으로 static이 아닌 클래스와 동일하지만 인스턴스화할 수 없다는 한 가지 차이점이 있습니다.

2. 즉 new 키워드를 사용하여 이 클래스 형식의 변수를 만들 수 없습니다.

3. 인스턴스 변수가 없으므로 정적 클래스 멤버는 클래스 이름 자체를 사용하여 액세스합니다.

4. 예를 들어 MethodA라는 public 메서드가 있는 UtilityClass라는 정적 클래스가 있으면 다음 예제와 같이 이 메서드를 호출합니다.


UtilityClass.MethodA();


5. 입력 매개 변수에 따라 동작할 뿐 내부 인스턴스 필드를 가져오거나 설정할 필요가 없는 일련의 메서드에 대한 편리한 컨테이너로 정적 클래스를 사용할 수 있습니다.

6. 예를 들어 .NET Framework 클래스 라이브러리의 정적 System.Math 클래스에는 특정 Math 클래스 인스턴스에 고유한 데이터를 저장하거나 검색할 필요 없이 수학 연산을 수행하는 메서드가 포함되어 있습니다.

7. 즉, 다음 예제와 같이 클래스 이름 및 메서드 이름을 지정하여 클래스의 멤버를 적용합니다.


double dub = -3.14;
Console.WriteLine(Math.Abs(dub));
Console.WriteLine(Math.Floor(dub));
Console.WriteLine(Math.Round(Math.Abs(dub)));

// Output:
// 3.14
// -4
// 3


8. 모든 클래스 형식의 경우와 같이 정적 클래스의 형식 정보는 이 클래스를 참조하는 프로그램이 로드될 때 .NET Framework CLR(공용 언어 런타임)에 의해 로드됩니다.

9. 프로그램에서는 클래스가 로드되는 정확한 시점을 지정할 수 없습니다.

10. 하지만 프로그램에서 클래스를 처음 참조하기 전에 이 클래스는 반드시 로드되고 해당 필드가 초기화되고 정적 생성자가 호출됩니다.

11. 정적 생성자는 한 번만 호출되며 정적 클래스는 프로그램이 속한 응용 프로그램 도메인의 수명 기간 동안 메모리에 유지됩니다.


참고

1. 하나의 인스턴스만 만들 수 있는 비정적 클래스를 만들려면 Implementing Singleton in C#을 참조하십시오.


12. 다음 목록에서는 정적 클래스의 주요 특징에 대해 설명합니다.

1. 정적 멤버만 포함합니다.

2. 인스턴스화할 수 없습니다.

3. 봉인되어 있습니다.

4.인스턴스 생성자를 포함할 수 없습니다.

13. 따라서 정적 클래스를 만드는 과정은 정적 멤버와 전용 생성자만 들어 있는 클래스를 만드는 과정과 기본적으로 비슷합니다.

14. 전용 생성자는 클래스가 인스턴스화되지 않도록 합니다.

15. 정적 클래스를 사용하면 인스턴스 멤버가 실수로 추가되지 않았는지 컴파일러에서 검사할 수 있다는 이점이 있습니다.

16. 컴파일러에서는 이 클래스의 인스턴스가 생성되지 않도록 보장합니다.

17. 정적 클래스는 봉인 클래스이므로 상속될 수 없습니다.

18. 정적 클래스는 Object에서만 상속될 수 있습니다.

19. 정적 클래스는 인스턴스 생성자를 포함할 수 없지만 정적 생성자는 포함할 수 있습니다.

20. 특수한 초기화가 필요한 정적 멤버가 있는 비정적 클래스에서는 정적 생성자도 정의해야 합니다.

21. 자세한 내용은 정적 생성자(C# 프로그래밍 가이드)를 참조하십시오.


예제

1. 다음은 온도를 섭씨에서 화씨로 또는 화씨에서 섭씨로 변환하는 메서드 두 개가 들어 있는 정적 클래스의 예제입니다.


public static class TemperatureConverter
{
    public static double CelsiusToFahrenheit(string temperatureCelsius)
    {
        // Convert argument to double for calculations.
        double celsius = Double.Parse(temperatureCelsius);

        // Convert Celsius to Fahrenheit.
        double fahrenheit = (celsius * 9 / 5) + 32;

        return fahrenheit;
    }

    public static double FahrenheitToCelsius(string temperatureFahrenheit)
    {
        // Convert argument to double for calculations.
        double fahrenheit = Double.Parse(temperatureFahrenheit);

        // Convert Fahrenheit to Celsius.
        double celsius = (fahrenheit - 32) * 5 / 9;

        return celsius;
    }
}

class TestTemperatureConverter
{
    static void Main()
    {
        Console.WriteLine("Please select the convertor direction");
        Console.WriteLine("1. From Celsius to Fahrenheit.");
        Console.WriteLine("2. From Fahrenheit to Celsius.");
        Console.Write(":");

        string selection = Console.ReadLine();
        double F, C = 0;

        switch (selection)
        {
            case "1":
                Console.Write("Please enter the Celsius temperature: ");
                F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine());
                Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
                break;

            case "2":
                Console.Write("Please enter the Fahrenheit temperature: ");
                C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine());
                Console.WriteLine("Temperature in Celsius: {0:F2}", C);
                break;

            default:
                Console.WriteLine("Please select a convertor.");
                break;
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Example Output:
    Please select the convertor direction
    1. From Celsius to Fahrenheit.
    2. From Fahrenheit to Celsius.
    :2
    Please enter the Fahrenheit temperature: 20
    Temperature in Celsius: -6.67
    Press any key to exit.
 */


정적 멤버

1. 비정적 클래스는 정적 메서드, 필드, 속성 또는 이벤트를 포함할 수 있습니다.

2. 클래스의 인스턴스를 만들지 않은 경우에도 클래스의 정적 멤버를 호출할 수 있습니다.

3. 정적 멤버는 언제나 인스턴스 이름이 아니라 클래스 이름을 사용하여 액세스됩니다.

4. 클래스의 인스턴스가 몇 개 만들어졌는지에 관계없이 정적 멤버는 하나만 존재합니다.

5. 정적 메서드 및 속성은 포함하는 형식의 비정적 필드 및 이벤트에 액세스할 수 없으며 메서드 매개 변수로 명시적으로 전달되지 않는 한 어떤 개체의 인스턴스 변수에도 액세스할 수 없습니다.

6. 전체 클래스를 static으로 선언하는 것보다 정적 멤버가 일부 포함된 비정적 클래스를 선언하는 것이 더 일반적입니다.

7. 정적 필드를 사용하는 두 가지 일반적인 경우는 인스턴스화된 개체 수를 보관하는 경우와 모든 인스턴스에서 공유해야 하는 변수를 저장하는 경우입니다.

8. 정적 메서드는, 클래스 인스턴스가 아닌 클래스 소속이기 때문에, 오버로드는 가능하지만 재정의는 불가능 합니다.

9. 필드를 static const로 선언할 수는 없지만 const 필드가 보여 주는 동작은 본질적으로 정적입니다.

10. const 필드는 형식에 속해 있지 형식의 인스턴스에 속해 있지 않습니다.

11. 따라서 const 필드는 정적 필드에 사용하는 것과 동일한 ClassName.MemberName 표기법을 사용하여 액세스할 수 있습니다.

12. 개체 인스턴스는 필요하지 않습니다.

13. C#은 정적 지역 변수(메서드 범위 내에서 선언되는 변수)를 지원하지 않습니다.

14. 정적 클래스 멤버는 다음과 같이 멤버의 반환 형식 앞에 static 키워드를 사용하여 선언합니다.


public class Automobile
{
    public static int NumberOfWheels = 4;
    public static int SizeOfGasTank
    {
        get
        {
            return 15;
        }
    }
    public static void Drive() { }
    public static event EventType RunOutOfGas;

    // Other non-static fields and properties...
}


15. 정적 멤버는 처음 액세스되기 전에 초기화되고 정적 생성자가 있으면 이 생성자를 호출하기 전에 초기화됩니다.

16. 정적 클래스 멤버에 액세스하려면 다음 예제에서와 같이 변수 이름 대신 클래스 이름을 사용하여 멤버의 위치를 지정해야 합니다.


Automobile.Drive();
int i = Automobile.NumberOfWheels;


17. 클래스에 정적 필드가 있으면 클래스가 로드될 때 해당 필드를 초기화하는 정적 생성자를 제공하십시오.

18. 정적 메서드를 호출하면 MSIL(Microsoft Intermediate Language)로 호출 명령이 생성되는 반면 인스턴스 메서드를 호출하면 역시 null 개체 참조를 확인하는 callvirt 명령이 생성됩니다.

19. 하지만 대부분의 경우 이 둘 사이의 성능 차이는 크지 않습니다.




멤버


1. 클래스와 구조체에는 해당 데이터와 동작을 나타내는 멤버가 있습니다.

2. 클래스 멤버에는 클래스에서 선언된 모든 멤버와 클래스 상속 계층 구조에 있는 모든 클래스에서 선언된 모든 멤버(생성자와 소멸자는 제외)가 포함됩니다.

3. 기본 클래스의 private 멤버는 상속되지만 파생 클래스에서 액세스할 수 없습니다.

4. 다음 표에서는 클래스나 구조체에 포함될 수 있는 멤버의 종류를 보여 줍니다.


멤버

설명

필드(C# 프로그래밍 가이드)

필드는 클래스 범위에 선언된 변수입니다. 필드는 기본 제공 숫자 형식일 수도 있고 다른 클래스의 인스턴스일 수도 있습니다. 예를 들어, 달력 클래스에는 현재 날짜가 들어 있는 필드가 포함될 수 있습니다.

상수(C# 프로그래밍 가이드)

상수는 컴파일 타임에 값이 설정되는 필드 또는 속성이므로 변경될 수 없습니다.

속성(C# 프로그래밍 가이드)

속성은 해당 클래스의 필드처럼 액세스할 수 있는 클래스의 메서드입니다. 속성을 사용하면 개체에 알리지 않은 채 클래스 필드가 변경되지 않도록 보호할 수 있습니다.

메서드(C# 프로그래밍 가이드)

메서드는 클래스가 수행할 수 있는 작업을 정의합니다. 메서드에는 입력 데이터를 제공하는 매개 변수를 사용할 수 있고, 메서드에서 매개 변수를 통해 출력 데이터를 반환할 수 있습니다. 메서드는 매개 변수를 사용하지 않고 직접 값을 반환할 수도 있습니다.

이벤트(C# 프로그래밍 가이드)

이벤트를 사용하면 단추 클릭, 메서드 작업 완료 등과 같은 사건에 대한 정보를 다른 개체에 알릴 수 있습니다. 이벤트는 대리자를 사용하여 정의되고 트리거됩니다.

연산자(C# 프로그래밍 가이드)

오버로드된 연산자는 클래스 멤버로 간주됩니다. 연산자를 오버로드할 때 클래스에서 해당 연산자를 public static 메서드로 정의합니다. 미리 정의된 연산자(+*< 등)는 멤버로 간주되지 않습니다. 자세한 내용은 오버로드할 수 있는 연산자(C# 프로그래밍 가이드)을 참조하십시오.

인덱서(C# 프로그래밍 가이드)

인덱서를 사용하면 배열과 비슷한 방식으로 개체를 인덱싱할 수 있습니다.

생성자(C# 프로그래밍 가이드)

생성자는 개체를 처음 만들 때 호출되는 메서드입니다. 생성자는 일반적으로 개체의 데이터를 초기화하는 데 사용됩니다.

소멸자(C# 프로그래밍 가이드)

소멸자는 C#에서 매우 드물게 사용됩니다. 소멸자는 개체를 메모리에서 제거하려 할 때 런타임 실행 엔진을 통해 호출되는 메서드입니다. 일반적으로 소멸자는 해제해야 할 리소스가 모두 올바르게 처리되도록 하는 데 사용됩니다.

중첩 형식(C# 프로그래밍 가이드)

중첩 형식은 다른 형식의 내부에 선언된 형식입니다. 중첩 형식은 일반적으로 이를 포함하는 형식에서만 사용하는 개체를 설명하는 데 사용됩니다.




액세스 한정자


1. 모든 형식 및 형식 멤버에는 현재 어셈블리나 다른 어셈블리에서 사용할 수 있는지 여부를 제어하는 액세스 수준이 있습니다.

2. 다음 액세스 한정자 중 하나를 사용하여 형식이나 멤버를 선언할 때 액세스 수준을 지정할 수 있습니다.

1) public

동일한 어셈블리의 다른 코드나 해당 어셈블리를 참조하는 다른 어셈블리의 코드에서 형식이나 멤버에 액세스할 수 있습니다.

2) private

동일한 클래스 또는 구조체의 코드에서만 형식이나 멤버에 액세스할 수 있습니다.

3) protected

동일한 클래스나 구조체의 코드 또는 파생 클래스의 클래스에서만 형식이나 멤버에 액세스할 수 있습니다.

4) internal

동일한 어셈블리의 코드에서는 형식이나 멤버에 액세스할 수 있지만 다른 어셈블리의 코드에서는 액세스할 수 없습니다.

5) protected internal

형식 또는 멤버는 선언되는 어셈블리의 모든 코드에 액세스하거나 다른 어셈블리의 파생 클래스 내에서 액세스할 수 있습니다. 다른 어셈블리로부터의 액세스는, 반드시 protected internal 요소가 선언된 클래스에서 파생한 클래스 선언 내에서 발생해야 하며, 또한 반드시 이렇게 파생된 클래스 형식의 인스턴스를 통해서 발생해야 합니다.

3. 다음 예제에서는 형식 및 멤버에 대한 액세스 한정자를 지정하는 방법을 보여 줍니다.


public class Bicycle
{
    public void Pedal() { }
}


4. 모든 컨텍스트의 모든 형식 또는 멤버에서 모든 액세스 한정자를 사용할 수 있는 것은 아닙니다.

5. 일부 경우에는 멤버를 포함하는 형식의 액세스 가능성에 의해 형식 멤버의 액세스 가능성이 제한됩니다.

6. 다음 단원에서는 액세스 가능성에 대해 자세히 설명합니다.


클래스 및 구조체 액세스 가능성

1. 네임스페이스 내부에서 직접 선언되어 다른 클래스나 구조체에 중첩되지 않은 클래스와 구조체는 public 또는 internal일 수 있습니다.

2. 액세스 한정자가 지정되지 않으면 internal이 기본적으로 사용됩니다.

3. 중첩된 클래스 및 구조체를 포함하는 구조체 멤버는 public, internal 또는 private으로 선언할 수 있습니다.

4. 중첩된 클래스 및 구조체를 포함한 클래스 멤버는 public, protected internal, protected, internal 또는 private이 될 수 있습니다.

5. 클래스 멤버 및 중첩된 클래스 및 구조체를 포함하는 구조체 멤버에 대한 액세스 수준은 기본적으로 private입니다.

6. private으로 선언된 중첩된 형식은 포함하는 형식 외부에서 액세스할 수 없습니다.

7. 파생 클래스의 액세스 가능성이 해당 기본 형식보다 좋을 수 없습니다.

8. 예를 들어 내부 클래스 A에서 공용 클래스 B를 파생시킬 수 없습니다.

9. 이러한 선언이 허용되면 파생 클래스에서 A의 모든 protected 또는 internal 멤버에 액세스할 수 있으므로 A를 public으로 만드는 것이나 마찬가지입니다.

10. InternalsVisibleToAttribute를 사용하면 다른 특정 어셈블리에서 internal 형식에 액세스하도록 허용할 수 있습니다.

11. 자세한 내용은 Friend 어셈블리(C# 및 Visual Basic)를 참조하십시오.



클래스 및 구조체 멤버 액세스 가능성

1. 다섯 가지 액세스 형식 중 하나로 클래스 멤버(중첩된 클래스와 구조체 포함)를 선언할 수 있습니다.

2. 구조체는 상속을 지원하지 않으므로 구조체 멤버는 protected로 선언할 수 없습니다.

3. 일반적으로 멤버의 액세스 가능성은 멤버가 포함된 형식의 액세스 가능성보다 크지 않습니다.

4. 그러나 내부 클래스의 공용 구성원은 구성원이 인터페이스 메서드를 구현하거나 공용 기본 클래스에 정의된 가상 메서드를 재정의하는 경우 어셈블리 외부에서 액세스할 수 있습니다.

5. 필드, 속성 또는 이벤트는 최소한 멤버 자체로 액세스할 수 있어야 합니다.

6. 마찬가지로, 반환 형식과, 메서드, 인덱서 또는 대리자인 모든 멤버의 매개 변수 형식은 멤버 자체만큼 액세스할 수 있어야 합니다.

7. 예를 들어 public 메서드 M에서 클래스 C를 반환하려면 C도 public이어야 합니다.

8. 마찬가지로 형식 A가 private으로 선언된 경우 A의 protected 속성이 존재할 수 없습니다.

사용자 정의 연산자는 항상 public으로 선언되어야 합니다. 자세한 내용은 operator(C# 참조)를 참조하십시오.

소멸자에는 액세스 한정자를 사용할 수 없습니다.

클래스나 구조체 멤버의 액세스 수준을 설정하려면 다음 예제와 같이 멤버 선언에 적절한 키워드를 추가합니다.


// public class:
public class Tricycle
{
    // protected method:
    protected void Pedal() { }

    // private field:
    private int wheels = 3;

    // protected internal property:
    protected internal int Wheels
    {
        get { return wheels; }
    }
}


참고

1. 보호된 내부 액세스 가능성 수준은 protected AND internal이 아니라 protected OR internal을 나타냅니다.

2. 즉, 보호되는 내부 멤버는 파생 클래스를 포함하여 동일한 어셈블리의 모든 클래스에서 액세스할 수 있습니다.

3. 동일한 어셈블리의 파생 클래스에서만 액세스할 수 있도록 제한하려면 클래스 자체를 internal로 선언하고 클래스의 멤버를 protected로 선언합니다.


기타 형식

1. 네임스페이스 안에 직접 선언된 인터페이스는 public 또는 internal로 선언할 수 있으며, 클래스 및 구조체와 마찬가지로 인터페이스는 기본적으로 internal 액세스입니다.

2. 인터페이스의 용도는 다른 형식이 클래스 또는 구조체에 액세스할 수 있게 하는 것이므로 인터페이스 멤버는 항상 public입니다.

3. 인터페이스 멤버에는 액세스 한정자를 적용할 수 없습니다.

4. 열거형 멤버는 항상 public이고 액세스 한정자를 적용할 수 없습니다.

5. 대리자는 클래스 및 구조체와 같이 동작합니다.

6. 기본적으로 네임스페이스 내에서 직접 선언하면 내부 액세스할 수 있으며 중첩되면 private 액세스 권한을 갖습니다.




필드


1. 필드는 클래스 또는 구조체 내부에 직접 선언되는 임의 형식의 변수입니다.

2. 필드는 포함하는 형식의 멤버 입니다.

3. 클래스 또는 구조체는 인스턴스 필드 또는 정적 필드를 가지거나 둘 모두를 가질 수 있습니다.

4. 인스턴스 필드는 특정 형식의 인스턴스에 고유합니다.

5. 인스턴스 필드 F를 포함하는 클래스 T가 있을 때 T 형식의 개체 두 개를 만드는 경우 다른 개체의 값에 영향을 주지 않고 각 개체의 F 값을 수정할 수 있습니다.

6. 이와 달리 정적 필드는 클래스 자체에 속해 있으며 해당 클래스의 모든 인스턴스에서 공유됩니다.

7. 인스턴스 A에서 정적 필드를 변경하는 경우 인스턴스 B와 C에서 이 필드에 액세스할 때 즉시 이 변경 내용을 볼 수 있습니다.

8. 일반적으로 액세스 가능성이 private 또는 protected인 변수에 대해서만 필드를 사용해야 합니다.

9. 클래스에서 클라이언트 코드에 노출하는 데이터는 메서드, 속성 및 인덱서를 통해 제공해야 합니다.

10. 내부 필드에 간접적으로 액세스하기 위해 이러한 구문을 사용함으로써 잘못된 입력 값으로부터 보호할 수 있습니다.

11. public 속성에 의해 노출되는 데이터를 저장하는 private 필드를 지원 저장소(backing store) 또는 지원 필드(backing field)라고 합니다.

12. 대개 필드에는 여러 클래스 메서드에서 액세스해야 하고 단일 메서드의 수명보다 오래 저장되어야 하는 데이터가 저장됩니다.

13. 예를 들어 달력 데이터를 나타내는 클래스에는 각각 일, 월, 년을 나타내는 세 개의 정수 필드가 포함될 수 있습니다.

14. 단일 메서드 범위 밖에서 사용하지 않는 변수는 메서드 본문의 내부에서 지역 변수로 선언해야 합니다.

15. 필드는 필드의 액세스 수준을 지정하고 필드의 형식과 필드의 이름을 차례로 사용하여 클래스 블록에서 선언됩니다.

16. 예를 들면 다음과 같습니다.


public class CalendarEntry
{
    // private field
    private DateTime date;

    // public field (Generally not recommended.)
    public string day;

    // Public property exposes date field safely.
    public DateTime Date 
    {
        get 
        {
            return date;
        }
        set 
        {
            // Set some reasonable boundaries for likely birth dates.
            if (value.Year > 1900 && value.Year <= DateTime.Today.Year)
            {
                date = value;
            }
            else
                throw new ArgumentOutOfRangeException();
        }

    }

    // Public method also exposes date field safely.
    // Example call: birthday.SetDate("1975, 6, 30");
    public void SetDate(string dateString)
    {
        DateTime dt = Convert.ToDateTime(dateString);

        // Set some reasonable boundaries for likely birth dates.
        if (dt.Year > 1900 && dt.Year <= DateTime.Today.Year)
        {
            date = dt;
        }
        else
            throw new ArgumentOutOfRangeException();
    }

    public TimeSpan GetTimeSpan(string dateString)
    {
        DateTime dt = Convert.ToDateTime(dateString);

        if (dt != null && dt.Ticks < date.Ticks)
        {
            return date - dt;
        }
        else
            throw new ArgumentOutOfRangeException();  

    }
}


17. 개체의 필드에 액세스하려면 objectname.fieldname과 같이 개체 이름 뒤에 마침표와 필드 이름을 추가합니다.

18. 예를 들면 다음과 같습니다.


CalendarEntry birthday = new CalendarEntry();
birthday.day = "Saturday";


19. 필드를 선언할 때 대입 연산자를 사용하여 필드의 초기 값을 지정할 수 있습니다.

20. 예를 들어 day 필드에 "Monday"을 자동으로 할당하려면 다음 예제와 같이 day를 선언합니다.


public class CalendarDateWithInitialization
{
    public string day = "Monday";
    //...
}


21. 필드는 개체 인스턴스의 생성자를 호출하기 직전에 초기화됩니다.

22. 생성자가 필드의 값을 할당하는 경우 필드 선언 도중 지정된 모든 값을 덮어씁니다.

23. 자세한 내용은 생성자 사용(C# 프로그래밍 가이드)를 참조하십시오.



참고

1. 필드 이니셜라이저는 다른 인스턴스 필드를 참조할 수 없습니다.


24. 필드는 public, private, protected, internal 또는 protected internal로 표시할 수 있습니다.

25. 이러한 액세스 한정자는 클래스 사용자가 필드에 액세스하는 방식을 정의합니다.

26. 자세한 내용은 액세스 한정자(C# 프로그래밍 가이드)를 참조하십시오.

27. 추가적으로 필드를 static으로 선언할 수도 있습니다.

28. 이렇게 하면 클래스의 인스턴스가 없어도 언제든지 호출자가 필드를 사용할 수 있습니다.

29. 자세한 내용은 정적 클래스 및 정적 클래스 멤버(C# 프로그래밍 가이드)를 참조하십시오.

30. readonly를 사용하여 필드를 선언할 수 있습니다.

31. 읽기 전용 필드에는 초기화 도중이나 생성자에서만 값을 할당할 수 있습니다.

32. static readonly 필드는 상수와 매우 비슷하지만 C# 컴파일러가 컴파일 타임이 아닌 런타임에만 정적 읽기 전용 필드의 값에 액세스할 수 있다는 점에서 차이가 있습니다.

33. 자세한 내용은 상수(C# 프로그래밍 가이드)를 참조하십시오.




상수


1. 상수는, 컴파일 시점에 인식되며 프로그램 수명기간 중에 변경되지 않는 불변값입니다.

2. 상수는 const 한정자를 사용하여 선언합니다.

3. C# 기본 제공 형식(System.Object 제외)만 const로 선언할 수 있습니다.

4. 기본 제공 형식의 목록을 보려면 기본 제공 형식 표(C# 참조)를 참조하십시오.

5. 클래스, 구조체, 배열을 비롯한 사용자 정의 형식은 const가 될 수 없습니다.

6. 런타임에 생성자 내부 등에서 한 번 초기화되고 그 이후로는 변경할 수 없는 클래스, 구조체 또는 배열을 만들려면 readonly 한정자를 사용하십시오.

7. C#에서는 const 메서드, 속성 또는 이벤트를 사용할 수 없습니다.

8. 열거형을 사용하면 정수 계열 기본 제공 형식(예: int, uint, long)에 대해 명명된 상수를 정의할 수 있습니다.

9. 자세한 내용은 enum(C# 참조)을 참조하십시오.

10. 상수를 선언할 때 이를 초기화해야 합니다.

11. 예를 들면 다음과 같습니다.


class Calendar1
{
    public const int months = 12;
}


12. 이 예제에서 상수 months는 항상 12입니다.

13. 이 값은 클래스 자체에서도 변경할 수 없습니다.

14. 컴파일러는 C# 소스 코드에서 상수 식별자(예: months)를 만나면 이 리터럴 값을 직접 컴파일러에서 생성하는 IL(Intermediate Language) 코드로 바꿉니다.

15. 런타임에 상수에 연결된 변수 주소가 없으므로 const 필드를 참조로 전달하거나 식의 l-value로 사용할 수 없습니다.


참고

1. DLL과 같은 다른 코드에 정의된 상수 값을 참조할 때는 매우 주의해야 합니다.

2. DLL 새 버전에서 해당 상수에 대해 새로운 값을 정의하는 경우 사용자 프로그램에서는 새 버전을 사용하여 다시 컴파일하기 전까지 이전 리터럴 값을 보유하게 됩니다.


16. 동일한 형식의 상수 여러 개를 동시에 선언할 수 있습니다.

17. 예를 들면 다음과 같습니다.


class Calendar2
{
    const int months = 12, weeks = 52, days = 365;
}


18. 순환 참조 문제가 발생하지 않는 한 상수를 초기화하는 데 사용되는 식에서 다른 상수를 참조할 수 있습니다.

19. 예를 들면 다음과 같습니다.


class Calendar3
{
    const int months = 12;
    const int weeks = 52;
    const int days = 365;

    const double daysPerWeek = (double) days / (double) weeks;
    const double daysPerMonth = (double) days / (double) months;
}


20. 상수는 public, private, protected, internal 또는 protectedinternal로 표시할 수 있습니다.

21. 이러한 액세스 한정자는 클래스 사용자가 상수에 액세스하는 방식을 정의합니다.

22. 자세한 내용은 액세스 한정자(C# 프로그래밍 가이드)를 참조하십시오.

23. 상수의 값은 해당 형식의 모든 인스턴스에서 동일하므로 상수는 static 필드처럼 액세스됩니다.

24. 상수는 static 키워드를 사용하여 선언하지 않습니다.

25. 상수를 정의하는 클래스에 포함되지 않은 식에서 상수에 액세스하려면 클래스 이름, 마침표 및 상수 이름을 사용해야 합니다.

26. 예를 들면 다음과 같습니다.


int birthstones = Calendar.months;




방법: C#에서 상수 정의


1. 상수는 컴파일 타임에 값이 설정되는 필드이므로 변경될 수 없습니다.

2. 상수를 사용하여 특별한 값에 대해 숫자 리터럴("마법수") 대신 의미 있는 이름을 지정합니다.


참고

1. C#에서 #define 전처리기 지시문은 C 및 C++에서 일반적으로 사용되는 방식으로 상수를 정의할 수 없습니다.


3. int, byte 등과 같은 정수 계열 형식의 상수 값을 정의하려면 열거 형식을 사용하십시오.

4. 자세한 내용은 enum(C# 참조)을 참조하십시오.

5. 비정수 상수를 정의하는 방법 중 하나는 이름이 Constants인 단일 정적 클래스로 그룹화하는 것입니다.

6. 다음 예제와 같이 상수에 대한 모든 참조는 클래스 이름으로 시작되어야 합니다.


static class Constants
{
    public const double Pi = 3.14159;
    public const int SpeedOfLight = 300000; // km per sec.

}
class Program
{
    static void Main()
    {
        double radius = 5.3;
        double area = Constants.Pi * (radius * radius);
        int secsFromSun = 149476000 / Constants.SpeedOfLight; // in km
    }
}


7. 클래스 이름 한정자를 사용하면 상수를 사용하는 사용자에게 그것이 상수 이고 상수를 수정할 수 없음을 이해하는데 도움이 됩니다.



속성


1. 속성은 전용 필드의 값을 읽거나 쓰거나 계산하는 유연한 메커니즘을 제공하는 멤버입니다.

2. 공용 데이터 멤버인 것처럼 속성을 사용할 수 있지만, 실제로 접근자라는 특수 메서드입니다.

3. 이렇게 하면 데이터에 쉽게 액세스할 수 있으며 메서드의 안전성과 유연성 수준을 올리는 데에도 도움이 됩니다.

4. 이 예제에서 TimePeriod 클래스는 기간을 저장합니다.

5. 내부적으로 이 클래스는 초 단위로 시간을 저장하지만, Hours라는 속성을 사용하여 클라이언트에서 시간 단위로 시간을 지정할 수 있습니다.

6 Hours 속성에 대한 접근자는 시간과 초 사이의 변환을 수행합니다.


class TimePeriod
{
    private double seconds;

    public double Hours
    {
        get { return seconds / 3600; }
        set { seconds = value * 3600; }
    }
}


class Program
{
    static void Main()
    {
        TimePeriod t = new TimePeriod();

        // Assigning the Hours property causes the 'set' accessor to be called.
        t.Hours = 24;

        // Evaluating the Hours property causes the 'get' accessor to be called.
        System.Console.WriteLine("Time in hours: " + t.Hours);
    }
}
// Output: Time in hours: 24


식 본문 정의

1. 일반적으로 식의 결과와 함께 바로 반환되는 속성이 있습니다. 

2. =>를 사용하여 이러한 속성을 정의하기 위한 구문 바로 가기는 다음과 같습니다.


public string Name => First + " " + Last; 


3. 속성은 읽기 전용이어야 하며, get 접근자 키워드를 사용하지 않습니다.


속성 개요


1. 속성을 사용하면 클래스가 구현 또는 검증 코드를 숨기는 동시에 값을 가져오고 설정하는 방법을 공개적으로 노출할 수 있습니다.

2. get 속성 접근자는 속성 값을 반환하는 데 사용되고 set 접근자는 새 값을 할당하는 데 사용됩니다.

3. 이러한 접근자는 각기 다른 액세스 수준을 가질 수 있습니다.

4. 자세한 내용은 접근자 액세스 가능성 제한(C# 프로그래밍 가이드)을 참조하세요.

5. value 키워드는 set 접근자가 할당하는 값을 정의하는 데 사용됩니다.

6. set 접근자를 구현하지 않는 속성은 읽기 전용입니다.

7. 사용자 지정 접근자 코드가 필요 없는 단순 속성의 경우 자동 구현 속성을 사용하는 것이 좋습니다.

8. 자세한 내용은 자동으로 구현된 속성(C# 프로그래밍 가이드)을 참조하세요.



속성 사용


1. 속성은 fields와 methods의 특징을 모두 갖고 있습니다.

2. 개체의 사용자에게는 속성이 필드로 나타나며, 속성에 액세스하려면 동일한 구문이 필요합니다.

3. 클래스의 구현자에게 있어 속성은 get 접근자 및/또는 set 접근자를 나타내는 한 개 또는 두 개의 코드 블록입니다.

4. get 접근자의 코드 블록은 속성을 읽을 때 실행되고, set 접근자의 코드 블록은 속성에 새 값을 할당할 때 실행됩니다.

5. set 접근자가 없는 속성은 읽기 전용으로 간주됩니다.

6. get 접근자가 없는 속성은 쓰기 전용으로 간주됩니다.

7. 두 접근자가 모두 있는 속성은 읽고 쓸 수 있습니다.

8. 필드와 달리 속성은 변수로 분류되지 않습니다.

9. 따라서 ref(C# 참조) 또는 out(C# 참조) 매개 변수로 속성을 전달할 수 없습니다.

10. 속성은 여러 가지로 사용할 수 있습니다.

11. 속성을 사용하면 데이터 변경을 허용하기 전에 데이터의 유효성을 검사하거나, 데이터베이스 같은 다른 소스에서 해당 데이터가 실제로 검색된 클래스에 데이터를 투명하게 노출하거나, 이벤트 발생 또는 다른 필드의 값 변경 같이 데이터가 변경되는 경우에 작업을 수행할 수 있습니다.

12. 클래스 블록에서 필드의 액세스 수준을 지정하고 속성의 형식, 속성의 이름을 차례로 지정한 다음 get 접근자 및/또는 set 접근자를 선언하는 코드 블록을 추가하여 속성을 선언할 수 있습니다.

13. 예를 들면 다음과 같습니다.


public class Date
{
    private int month = 7;  // Backing store

    public int Month
    {
        get
        {
            return month;
        }
        set
        {
            if ((value > 0) && (value < 13))
            {
                month = value;
            }
        }
    }
}


8. 이 예제에서 Month는 속성으로 선언되며 set 접근자는 Month 값이 1에서 12 사이의 숫자로 설정되는지 확인할 수 있습니다.

9. Month 속성은 실제 값을 추적하기 위해 전용 필드를 사용합니다.

10. 속성 데이터의 실제 위치를 일반적으로 속성의 "지원 저장소"라고 합니다.

11. 속성의 지원 저장소로는 대개 전용 필드가 사용됩니다.

12. 이 필드는 속성을 호출한 경우에만 변경할 수 있도록 전용으로 표시됩니다.

13. 공용 및 전용 액세스 제한 사항에 대한 자세한 내용은 액세스 한정자(C# 프로그래밍 가이드)를 참조하십시오.

14. 자동으로 구현된 속성을 사용하면 단순화된 구문으로 간단한 속성을 선언할 수 있습니다.

15. 자세한 내용은 자동으로 구현된 속성(C# 프로그래밍 가이드)을 참조하십시오.


get 접근자

1. get 접근자의 본문은 메서드의 본문과 비슷하며 속성 형식의 값을 반환해야 합니다.

2. get 접근자를 실행하는 것은 필드 값을 읽는 것과 같습니다.

3. 예를 들어, 최적화 기능을 활성화한 상태로 get 접근자에서 private 변수를 반환하는 경우 메서드 호출 오버헤드가 발생하지 않도록 get 접근자 메서드에 대한 호출이 컴파일러에서 인라인됩니다.

4. 그러나 가상 get 접근자 메서드는 인라인될 수 없습니다.

5. 런타임에 실제로 호출할 수 있는 메서드에 대한 정보가 컴파일 단계에서 컴파일러에 제공되지 않기 때문입니다. 6. 다음은 전용 필드인 name의 값을 반환하는 get 접근자입니다.


class Person
{
    private string name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return name;
        }
    }
}


7. 할당 대상인 경우를 제외하고 속성을 참조하면 속성 값을 읽기 위해 get 접근자가 호출됩니다.

8. 예를 들면 다음과 같습니다.


Person person = new Person();
//...

System.Console.Write(person.Name);  // the get accessor is invoked here


9. get 접근자는 return 또는 throw 문으로 끝나야 합니다. 제어는 접근자 본문에서 끝납니다.

10. get 접근자를 사용하여 개체의 상태를 변경하는 것은 잘못된 프로그래밍 습관입니다.

12. 예를 들어, 다음 접근자에서는 number 필드에 액세스할 때마다 개체의 상태가 바뀌는 의도하지 않은 동작이 발생합니다.


private int number;
public int Number
{
    get
    {
        return number++;   // Don't do this
    }
}


13. get 접근자는 필드 값을 반환하거나 필드 값을 계산하여 그 결과를 반환하는 데 사용됩니다. 예를 들면 다음과 같습니다.


class Employee
{
    private string name;
    public string Name
    {
        get
        {
            return name != null ? name : "NA";
        }
    }
}


14. 위 코드 세그먼트에서 Name 속성에 값을 할당하지 않으면 NA 값이 반환됩니다.


set 접근자

1. set 접근자는 반환 형식이 void인 메서드와 비슷합니다.

2. 이 접근자는 해당 형식이 속성의 형식과 같은 value라는 암시적 매개 변수를 사용합니다.

3. 다음 예제에서는 Name 속성에 set 접근자를 추가합니다.


class Person
{
    private string name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}


4. 속성에 값을 할당하면 새 값을 제공하는 인수를 사용하여 set 접근자가 호출됩니다.

5. 예를 들면 다음과 같습니다.


Person person = new Person();
person.Name = "Joe";  // the set accessor is invoked here                

System.Console.Write(person.Name);  // the get accessor is invoked here


6. set 접근자의 지역 변수 선언에서 암시적 매개 변수 이름(value)을 사용하면 오류가 발생합니다.


설명

1. 속성은 public, private, protected, internal 또는 protected internal로 표시할 수 있습니다.

2. 이러한 액세스 한정자는 클래스 사용자가 속성에 액세스할 수 있는 방식을 정의합니다.

3. 동일한 속성에 대한 get 및 set 접근자에 서로 다른 액세스 한정자가 있을 수 있습니다.

4. 예를 들어, get은 형식 외부에서 읽기 전용으로 액세스할 수 있도록 public으로 지정하고 set은 private 또는 protected로 지정할 수 있습니다.

5. 자세한 내용은 액세스 한정자(C# 프로그래밍 가이드)를 참조하십시오.

6. static 키워드를 사용하면 속성을 정적 속성으로 선언할 수 있습니다.

7. 이렇게 하면 클래스의 인스턴스가 없는 경우에도 언제든지 호출자가 속성을 사용할 수 있습니다.

8. 자세한 내용은 정적 클래스 및 정적 클래스 멤버(C# 프로그래밍 가이드)를 참조하십시오.

9. virtual 키워드를 사용하면 속성을 가상 속성으로 선언할 수 있습니다.

10. 이렇게 하면 override 키워드를 사용하여 파생 클래스에서 속성 동작을 재정의할 수 있습니다.

11. 이러한 옵션에 대한 자세한 내용은 상속(C# 프로그래밍 가이드)을 참조하십시오.

12. 가상 속성을 재정의하는 속성은 파생 클래스에 대해 이 속성이 가상이 아님을 지정하는 sealed가 될 수도 있습니다.

13. 마지막으로 속성을 abstract로 선언할 수 있습니다.

14. 이렇게 하면 클래스에 구현이 없으므로 파생 클래스에서 자체 구현을 작성해야 합니다.

15. 이러한 옵션에 대한 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버(C# 프로그래밍 가이드)을 참조하십시오.


참고

1. 정적 속성의 접근자에 virtual(C# 참조), abstract(C# 참조) 또는 override(C# 참조) 한정자를 사용하면 오류가 발생합니다.


예제

1. 다음 예제에서는 인스턴스, 정적 및 읽기 전용 속성을 보여 줍니다. 키보드에서 직원 이름을 입력 받고, NumberOfEmployees 변수의 값을 1만큼 증가시킨 다음 직원 이름과 번호를 표시합니다.


public class Employee
{
    public static int NumberOfEmployees;
    private static int counter;
    private string name;

    // A read-write instance property:
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    // A read-only static property:
    public static int Counter
    {
        get { return counter; }
    }

    // A Constructor:
    public Employee()
    {
        // Calculate the employee's number:
        counter = ++counter + NumberOfEmployees;
    }
}

class TestEmployee
{
    static void Main()
    {
        Employee.NumberOfEmployees = 107;
        Employee e1 = new Employee();
        e1.Name = "Claude Vige";

        System.Console.WriteLine("Employee number: {0}", Employee.Counter);
        System.Console.WriteLine("Employee name: {0}", e1.Name);
    }
}
/* Output:
    Employee number: 108
    Employee name: Claude Vige
*/


예제

1. 다음 예제에서는 파생 클래스에 기본 클래스의 속성과 동일한 이름의 속성이 있어서 숨겨진 기본 클래스의 속성에 액세스하는 방법을 보여 줍니다.


public class Employee
{
    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

public class Manager : Employee
{
    private string name;

    // Notice the use of the new modifier:
    public new string Name
    {
        get { return name; }
        set { name = value + ", Manager"; }
    }
}

class TestHiding
{
    static void Main()
    {
        Manager m1 = new Manager();

        // Derived class property.
        m1.Name = "John";

        // Base class property.
        ((Employee)m1).Name = "Mary";

        System.Console.WriteLine("Name in the derived class is: {0}", m1.Name);
        System.Console.WriteLine("Name in the base class is: {0}", ((Employee)m1).Name);
    }
}
/* Output:
    Name in the derived class is: John, Manager
    Name in the base class is: Mary
*/


2. 위 예제의 주요 사항은 다음과 같습니다.

1) 파생 클래스의 Name 속성은 기본 클래스의 Name 속성을 숨깁니다. 이 경우 다음과 같이 new 한정자가 파생 클래스의 속성 선언에 사용됩니다.


public new string Name


2) (Employee) 캐스팅은 기본 클래스의 숨겨진 속성에 액세스하는 데 사용됩니다.


((Employee)m1).Name = "Mary";


3) 멤버 숨기기에 대한 자세한 내용은 new 한정자(C# 참조)를 참조하십시오.


예제

1. 다음 예제의 Cube 클래스와 Square는 Shape 추상 클래스를 구현하고 Area 추상 속성을 재정의합니다.

2. 속성에서 override 한정자를 사용한 방법에 주의하십시오. 프로그램에서는 측면 길이 값을 입력 받아 정사각형과 입방체의 면적을 계산합니다.

3. 또한 면적을 입력 받아 그에 대응하는 정사각형과 입방체의 측면 길이 값을 계산합니다.


abstract class Shape
{
    public abstract double Area
    {
        get;
        set;
    }
}

class Square : Shape
{
    public double side;

    public Square(double s)  //constructor
    {
        side = s;
    }

    public override double Area
    {
        get
        {
            return side * side;
        }
        set
        {
            side = System.Math.Sqrt(value);
        }
    }
}

class Cube : Shape
{
    public double side;

    public Cube(double s)
    {
        side = s;
    }

    public override double Area
    {
        get
        {
            return 6 * side * side;
        }
        set
        {
            side = System.Math.Sqrt(value / 6);
        }
    }
}

class TestShapes
{
    static void Main()
    {
        // Input the side:
        System.Console.Write("Enter the side: ");
        double side = double.Parse(System.Console.ReadLine());

        // Compute the areas:
        Square s = new Square(side);
        Cube c = new Cube(side);

        // Display the results:
        System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
        System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
        System.Console.WriteLine();

        // Input the area:
        System.Console.Write("Enter the area: ");
        double area = double.Parse(System.Console.ReadLine());

        // Compute the sides:
        s.Area = area;
        c.Area = area;

        // Display the results:
        System.Console.WriteLine("Side of the square = {0:F2}", s.side);
        System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
    }
}
/* Example Output:
    Enter the side: 4
    Area of the square = 16.00
    Area of the cube = 96.00

    Enter the area: 24
    Side of the square = 4.90
    Side of the cube = 2.00
*/




인터페이스 사용


1. interface(C# 참조)에서 속성을 선언할 수 있습니다.

2. 다음은 인터페이스 인덱서 접근자의 예제입니다.




3. 인터페이스 속성의 접근자에는 본문이 없습니다.

4. 접근자를 사용하는 목적은 속성이 읽기/쓰기, 읽기 전용 또는 쓰기 전용인지 여부를 나타내는 것입니다.


예제

1. 다음 예제의 IEmployee 인터페이스에는 읽기/쓰기 속성인 Name과 읽기 전용 속성인 Counter가 있습니다. 2. Employee 클래스는 IEmployee 인터페이스를 구현하고 이 두 속성을 사용합니다.

3. 프로그램에서는 새 직원 이름과 현재 번호를 읽어 들이고 직원 이름과 계산된 직원 번호를 표시합니다.

4. 멤버가 선언된 인터페이스를 참조하는 속성의 정규화된 이름을 사용할 수 있습니다.

5. 예를 들면 다음과 같습니다.


string IEmployee.Name
{
    get { return "Employee Name"; }
    set { }
}


6. 이를 명시적 인터페이스 구현(C# 프로그래밍 가이드)이라고 합니다.

7. 예를 들어, Employee 클래스가ICitizen 및 IEmployee라는 두 개의 인터페이스를 구현하며 두 인터페이스 모두에 Name 속성이 있는 경우 해당 인터페이스 멤버를 명시적으로 구현해야 합니다.

8. 예를 들면 다음과 같습니다.


string IEmployee.Name
{
    get { return "Employee Name"; }
    set { }
}


9. 위 선언에서는 IEmployee 인터페이스의 Name 속성을 구현합니다.


string ICitizen.Name
{
    get { return "Citizen Name"; }
    set { }
}


10. 그러나 이와 같이 선언하면 ICitizen 인터페이스의 Name 속성을 구현합니다.


interface IEmployee
{
    string Name
    {
        get;
        set;
    }

    int Counter
    {
        get;
    }
}

public class Employee : IEmployee
{
    public static int numberOfEmployees;

    private string name;
    public string Name  // read-write instance property
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    private int counter;
    public int Counter  // read-only instance property
    {
        get
        {
            return counter;
        }
    }

    public Employee()  // constructor
    {
        counter = ++counter + numberOfEmployees;
    }
}

class TestEmployee
{
    static void Main()
    {
        System.Console.Write("Enter number of employees: ");
        Employee.numberOfEmployees = int.Parse(System.Console.ReadLine());

        Employee e1 = new Employee();
        System.Console.Write("Enter the name of the new employee: ");
        e1.Name = System.Console.ReadLine();

        System.Console.WriteLine("The employee information:");
        System.Console.WriteLine("Employee number: {0}", e1.Counter);
        System.Console.WriteLine("Employee name: {0}", e1.Name);
    }
}


            210

Hazem Abolrous



샘플 출력

Enter number of employees: 210

Enter the name of the new employee: Hazem Abolrous

The employee information:

Employee number: 211

Employee name: Hazem Abolrous




접근자 액세스 가능성 제한


1. 속성이나 인덱서의 get 및 set 부분을 접근자라고 합니다.

2. 기본적으로 이러한 접근자의 표시 여부(액세스 수준)는 이 접근자가 속한 속성이나 인덱서의 경우와 동일합니다.

3. 자세한 내용은 액세스 가능성 수준을 참조하십시오.

4. 그러나 때로는 이러한 접근자 중 하나에 대한 액세스를 제한해야 할 수도 있습니다.

5. 일반적인 예로는 set 접근자의 액세스 가능성을 제한한 채 get 접근자를 계속 공용으로 액세스할 수 있도록 유지하는 경우를 들 수 있습니다.

6. 예를 들면 다음과 같습니다.


private string name = "Hello";

public string Name
{
    get
    {
        return name;
    }
    protected set
    {
        name = value;
    }
}


7. 이 예제에서 Name이라는 속성은 get 및 set 접근자를 정의합니다.

8. get 접근자는 속성 자체와 동일한 수준(이 경우 public)으로 액세스 가능성이 설정되는 반면, set 접근자는 이 접근자 자체에 protected 액세스 한정자를 적용하여 액세스가 명시적으로 제한됩니다.


접근자에 대한 액세스 한정자의 제한

1. 속성이나 인덱서에 대한 접근자 한정자를 사용하려면 다음과 같은 조건을 준수해야 합니다.

1) 인터페이스나 명시적 인터페이스 멤버 구현에는 접근자 한정자를 사용할 수 없습니다.

2) 접근자 한정자는 속성이나 인덱서에 set 및 get 접근자가 모두 있는 경우에만 사용할 수 있습니다. 이 경우 한정자는 두 접근자 중 하나에 대해서만 허용됩니다.

3) 속성이나 인덱서에 재정의 한정자가 있는 경우 재정의된 접근자의 접근자가 있으면 접근자 한정자가 이와 일치해야 합니다.

4) 접근자에 대한 액세스 수준은 속성이나 인덱서 자체에 대한 액세스 수준보다 더 제한적이어야 합니다.


재정의 접근자에 대한 액세스 한정자

1. 속성이나 인덱서를 재정의하는 경우 재정의 코드에서는 재정의된 접근자에 액세스할 수 있어야 합니다.

2. 또한 속성과 인덱서 모두의 액세스 수준 및 접근자의 액세스 수준은 상응하는 재정의된 속성과 인덱서 및 접근자의 액세스 수준과 일치해야 합니다.

3. 예를 들면 다음과 같습니다.


public class Parent
{
    public virtual int TestProperty
    {
        // Notice the accessor accessibility level.
        protected set { }

        // No access modifier is used here.
        get { return 0; }
    }
}
public class Kid : Parent
{
    public override int TestProperty
    {
        // Use the same accessibility level as in the overridden accessor.
        protected set { }

        // Cannot use access modifier here.
        get { return 0; }
    }
}


인터페이스 구현

1. 접근자를 사용하여 인터페이스를 구현하는 경우 접근자에는 액세스 한정자가 없을 수 있습니다.

2. 그러나 get 같은 접근자 하나를 사용하여 인터페이스를 구현하는 경우에는 다음 예제에서와 같이 다른 접근자에 액세스 한정자가 있을 수 있습니다.


public interface ISomeInterface
{
    int TestProperty
    {
        // No access modifier allowed here
        // because this is an interface.
        get;
    }
}

public class TestClass : ISomeInterface
{
    public int TestProperty
    {
        // Cannot use access modifier here because
        // this is an interface implementation.
        get { return 10; }

        // Interface property does not have set accessor,
        // so access modifier is allowed.
        protected set { }
    }
}


접근자 액세스 가능 도메인

1. 접근자에 대해 액세스 한정자를 사용하는 경우 접근자의 액세스 가능 도메인은 이 한정자를 통해 결정됩니다.

2. 접근자에 대해 액세스 한정자를 사용하지 않는 경우 접근자의 액세스 가능 도메인은 속성이나 인덱서의 액세스 수준에 따라 결정됩니다.


예제

1. 다음 예제에는 BaseClass, DerivedClass 및 MainClass라는 세 가지 클래스가 들어 있습니다.

2. BaseClass에 대한 속성 두 개가 있고 나머지 두 클래스에 모두 Name 및 Id가 있습니다.

3. 이 예제에서는 protected 또는 private 같은 제한된 액세스 한정자를 사용할 때 DerivedClass의 Id 속성을 BaseClass의 Id 속성으로 숨기는 방법을 보여 줍니다.

4. 따라서 이 속성에 값을 할당하면 BaseClass 클래스의 속성이 대신 호출됩니다.

5. 액세스 한정자를 public으로 바꾸면 속성에 액세스할 수 있습니다.

6. 이 예제에서는 DerivedClass에서 Name 속성의 set 접근자에 대한 private 또는 protected 같은 제한적 액세스 한정자로 접근자에 대한 액세스를 막고 여기에 할당하는 경우 오류를 생성하는 방법도 보여 줍니다.


public class BaseClass
{
    private string name = "Name-BaseClass";
    private string id = "ID-BaseClass";

    public string Name
    {
        get { return name; }
        set { }
    }

    public string Id
    {
        get { return id; }
        set { }
    }
}

public class DerivedClass : BaseClass
{
    private string name = "Name-DerivedClass";
    private string id = "ID-DerivedClass";

    new public string Name
    {
        get
        {
            return name;
        }

        // Using "protected" would make the set accessor not accessible. 
        set
        {
            name = value;
        }
    }

    // Using private on the following property hides it in the Main Class.
    // Any assignment to the property will use Id in BaseClass.
    new private string Id
    {
        get
        {
            return id;
        }
        set
        {
            id = value;
        }
    }
}

class MainClass
{
    static void Main()
    {
        BaseClass b1 = new BaseClass();
        DerivedClass d1 = new DerivedClass();

        b1.Name = "Mary";
        d1.Name = "John";

        b1.Id = "Mary123";
        d1.Id = "John123";  // The BaseClass.Id property is called.

        System.Console.WriteLine("Base: {0}, {1}", b1.Name, b1.Id);
        System.Console.WriteLine("Derived: {0}, {1}", d1.Name, d1.Id);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    Base: Name-BaseClass, ID-BaseClass
    Derived: John, ID-BaseClass
*/


설명

new private string Id 선언을 new public string Id로 바꾸는 경우 출력은 다음과 같습니다.

Name and ID in the base class: Name-BaseClass, ID-BaseClass

Name and ID in the derived class: John, John123




방법: 읽기/쓰기 속성 선언 및 사용


1. 속성을 사용하면 개체의 데이터에 보호되거나 제어되지 않고 확인되지 않은 방식으로 액세스하는 데 따른 위험 없이 공용 데이터 멤버의 이점을 누릴 수 있습니다.

2. 이 과정에는 내부 데이터 멤버에서 값을 할당 및 검색하기 위한 특별한 메서드인 접근자가 사용됩니다.

3. set 접근자를 사용하면 데이터 멤버를 할당할 수 있고 get 접근자를 사용하면 데이터 멤버 값을 검색할 수 있습니다.

4. 이 샘플에서는 문자열인 Name 및 정수인 Age라는 두 속성이 있는 Person 클래스를 보여 줍니다.

5. 두 속성은 모두 get 및 set 접근자를 제공하므로 이는 모두 읽기/쓰기 속성으로 간주됩니다.


예제

class Person
{
    private string name = "N/A";
    private int age = 0;

    // Declare a Name property of type string:
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    // Declare an Age property of type int:
    public int Age
    {
        get
        {
            return age;
        }

        set
        {
            age = value;
        }
    }

    public override string ToString()
    {
        return "Name = " + Name + ", Age = " + Age;
    }
}

class TestPerson
{
    static void Main()
    {
        // Create a new Person object:
        Person person = new Person();

        // Print out the name and the age associated with the person:
        Console.WriteLine("Person details - {0}", person);

        // Set some values on the person object:
        person.Name = "Joe";
        person.Age = 99;
        Console.WriteLine("Person details - {0}", person);

        // Increment the Age property:
        person.Age += 1;
        Console.WriteLine("Person details - {0}", person);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Person details - Name = N/A, Age = 0
    Person details - Name = Joe, Age = 99
    Person details - Name = Joe, Age = 100
*/


강력한 프로그래밍

1. 위 예제에서 Name과 Age 속성은 public이며 get 및 set 접근자를 모두 포함합니다.

2. 이렇게 하면 임의의 개체가 이들 속성을 읽고 쓸 수 있습니다.

3. 그러나 때로는 이러한 접근자 중 하나를 제외해야 할 수도 있습니다.

4. 예를 들어, set 접근자를 생략하면 속성을 읽기 전용으로 만들 수 있습니다.


public string Name
{
    get
    {
        return name;
    }
}


5. 또는 접근자 하나만 공용으로 노출시키고 다른 접근자는 전용 또는 보호된 상태로 만들 수 있습니다.

6. 자세한 내용은 비대칭 접근자 액세스 가능성을 참조하십시오.

7. 일단 속성이 선언되면 클래스의 필드처럼 사용할 수 있습니다.

8. 다음 문에서와 같이 속성 값을 구하고 설정할 때 기본 구문을 사용할 수 있습니다.


person.Name = "Joe";
person.Age = 99;


9. 속성의 set 메서드에서 특수한 value 변수를 사용할 수 있습니다.

10. 이 변수에는 사용자가 지정한 값이 포함됩니다. 예를 들어 다음과 같습니다.


name = value;


11. Person 개체의 Age 속성을 증분하는 데 필요한 clean 구문에 주의하십시오.


person.Age += 1;


12. 속성을 모델링할 때 set 및 get 메서드를 각각 사용한 경우에는 다음과 같은 코드를 사용해도 결과가 동일합니다.


person.SetAge(person.GetAge() + 1); 


13. 다음 예제에서는 ToString 메서드를 재정의합니다.


public override string ToString()
{
    return "Name = " + Name + ", Age = " + Age;
}


14. ToString은 프로그램에서 명시적으로 사용되지 않으며 WriteLine 호출에서 기본적으로 호출됩니다.



자동으로 구현된 속성


1. C# 3.0 이상에서는 속성 접근자에 추가적인 논리가 필요하지 않을 경우 자동 구현 속성을 통해 속성 선언이 더 간결해집니다.

2. 이를 통해 클라이언트 코드에서 개체를 만들 수도 있습니다.

3. 다음 예제와 같이 속성을 선언할 때 컴파일러는 속성의 get 및 set 접근자를 통해서만 액세스할 수 있는 private 익명 지원 필드를 만듭니다.


예제

1. 다음 예제에서는 일부 자동 구현 속성이 있는 간단한 클래스를 보여 줍니다.


// This class is mutable. Its data can be modified from
// outside the class.
class Customer
{
    // Auto-Impl Properties for trivial get and set
    public double TotalPurchases { get; set; }
    public string Name { get; set; }
    public int CustomerID { get; set; }

    // Constructor
    public Customer(double purchases, string name, int ID)
    {
        TotalPurchases = purchases;
        Name = name;
        CustomerID = ID;
    }
    // Methods
    public string GetContactInfo() {return "ContactInfo";}
    public string GetTransactionHistory() {return "History";}

    // .. Additional methods, events, etc.
}

class Program
{
    static void Main()
    {
        // Intialize a new object.
        Customer cust1 = new Customer ( 4987.63, "Northwind",90108 );

        //Modify a property
        cust1.TotalPurchases += 499.99;
    }
}


2. C# 6 이상 버전에서는 필드와 유사하게 자동 구현 속성을 초기화할 수 있습니다.


public string FirstName { get; set; } = "Jane";


3. 앞의 예제에 표시된 클래스는 변경할 수 있습니다.

4. 클라이언트 코드에서는 개체가 만들어진 후 개체의 값을 변경할 수 있습니다.

5. 데이터 및 중요 동작(메서드)을 포함하는 복잡한 클래스에는 공용 속성을 포함해야 할 수 있습니다.

6. 그러나 값 집합(데이터)만 캡슐화하고 동작이 적거나 없는 작은 클래스나 구조체의 경우 set 접근자를 private로 선언하거나(소비자에 대한 변경 불가능) get 접근자만 선언하여(생성자를 제외한 모든 위치에서 변경 불가능) 개체를 변경 불가능으로 설정해야 합니다.

7. 자세한 내용은 방법: 자동으로 구현된 속성을 사용하여 간단한 클래스 구현(C# 프로그래밍 가이드)을 참조하세요.

8. 특성은 자동 구현 속성에서 허용되지만, 소스 코드에서 액세스할 수 없으므로 지원 필드에서는 분명히 허용되지 않습니다.

9. 속성의 지원 필드에서 특성을 사용해야 할 경우 일반 속성만 만듭니다.



방법: 자동으로 구현된 속성을 사용하여 간단한 클래스 구현


1. 이 예제에서는 자동 구현 속성 집합을 캡슐화하는 데만 사용되는 변경할 수 없는 간단한 클래스를 만드는 방법을 보여 줍니다.

2. 참조 형식 의미 체계를 사용해야 하는 경우 구조체 대신 이러한 종류의 구문을 사용합니다.

3. 변경할 수 없는 속성은 두 가지 방법으로 만들 수 있습니다.

4. set 접근자를 private이 되도록 선언할 수 있습니다.

5. 속성은 형식 내에서만 설정할 수 있고 소비자는 변경할 수 없습니다.

6. 대신 get 접근자만 선언하여 형식의 생성자를 제외한 모든 위치에서 속성을 변경할 수 없도록 합니다.

7. private set 접근자를 선언하는 경우 개체 이니셜라이저를 사용하여 속성을 초기화할 수 없습니다.

8. 생성자나 팩터리 메서드를 사용해야 합니다.


예제

1. 다음 예제에서는 자동 구현 속성을 갖는 변경할 수 없는 클래스를 구현하는 두 가지 방법을 보여 줍니다.

2. 각 방법에서 속성 중 하나는 private set으로 선언하고 다른 하나는 get으로만 선언합니다.

3. 첫 번째 클래스는 생성자만 사용하여 속성을 초기화하고 두 번째 클래스는 생성자를 호출하는 정적 팩터리 메서드를 사용합니다.


// This class is immutable. After an object is created, 
    // it cannot be modified from outside the class. It uses a 
    // constructor to initialize its properties. 
    class Contact
    {
        // Read-only properties. 
        public string Name { get; }
        public string Address { get; private set; }

        // Public constructor. 
        public Contact(string contactName, string contactAddress)
        {
            Name = contactName;
            Address = contactAddress;               
        }
    }

    // This class is immutable. After an object is created, 
    // it cannot be modified from outside the class. It uses a 
    // static method and private constructor to initialize its properties.    
    public class Contact2
    {
        // Read-only properties. 
        public string Name { get; private set; }
        public string Address { get; }

        // Private constructor. 
        private Contact2(string contactName, string contactAddress)
        {
            Name = contactName;
            Address = contactAddress;               
        }

        // Public factory method. 
        public static Contact2 CreateContact(string name, string address)
        {
            return new Contact2(name, address);
        }
    }

    public class Program
    { 
        static void Main()
        {
            // Some simple data sources. 
            string[] names = {"Terry Adams","Fadi Fakhouri", "Hanying Feng", 
                              "Cesar Garcia", "Debra Garcia"};
            string[] addresses = {"123 Main St.", "345 Cypress Ave.", "678 1st Ave",
                                  "12 108th St.", "89 E. 42nd St."};

            // Simple query to demonstrate object creation in select clause. 
            // Create Contact objects by using a constructor. 
            var query1 = from i in Enumerable.Range(0, 5)
                        select new Contact(names[i], addresses[i]);

            // List elements cannot be modified by client code. 
            var list = query1.ToList();
            foreach (var contact in list)
            {
                Console.WriteLine("{0}, {1}", contact.Name, contact.Address);
            }

            // Create Contact2 objects by using a static factory method. 
            var query2 = from i in Enumerable.Range(0, 5)
                         select Contact2.CreateContact(names[i], addresses[i]);

            // Console output is identical to query1. 
            var list2 = query2.ToList();

            // List elements cannot be modified by client code. 
            // CS0272: 
            // list2[0].Name = "Eugene Zabokritski"; 

            // Keep the console open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();                
        }
    }

/* Output:
    Terry Adams, 123 Main St.
    Fadi Fakhouri, 345 Cypress Ave.
    Hanying Feng, 678 1st Ave
    Cesar Garcia, 12 108th St.
    Debra Garcia, 89 E. 42nd St.
*/


4. 컴파일러는 각 자동 구현 속성에 대해 지원 필드를 만듭니다.

5. 이 필드는 소스 코드에서 직접 액세스할 수 없습니다.




메서드


1. 메서드는 일련의 문을 포함하는 코드 블록입니다.

2. 프로그램을 통해 메서드를 호출하고 필요한 메서드 인수를 지정하여 문을 실행합니다.

3. C#에서는 실행된 모든 명령이 메서드의 컨텍스트에서 수행됩니다.

4. Main 메서드는 모든 C# 응용 프로그램의 진입점이고 프로그램이 시작될 때 CLR(공용 언어 런타임)에서 호출됩니다.


참고

1. 이 항목에서는 명명된 메서드에 대해 설명합니다.

2. 익명 함수에 대한 자세한 내용은 익명 함수(C# 프로그래밍 가이드)를 참조하세요.


메서드 서명

1. 메서드는 클래스 또는 구조체에서 public 또는 private 등의 액세스 수준, abstract 또는 sealed 등의 선택적 한정자, 반환 값, 메서드 이름 및 메서드 매개 변수를 지정하여 선언합니다.

2. 이들 파트는 함께 메서드 서명을 구성합니다.


참고

메서드의 반환 값은 메서드 오버로드를 위한 메서드 서명의 파트가 아닙니다. 그러나 대리자와 대리자가 가리키는 메서드 간의 호환성을 결정할 경우에는 메서드 서명의 파트입니다.


3. 메서드 매개 변수는 괄호로 묶고 쉼표로 구분합니다.

4. 빈 괄호는 메서드에 매개 변수가 필요하지 않음을 나타냅니다.

5. 이 클래스에는 다음 세 가지 메서드가 포함됩니다.


abstract class Motorcycle
{
    // Anyone can call this.
    public void StartEngine() {/* Method statements here */ }

    // Only derived classes can call this.
    protected void AddGas(int gallons) { /* Method statements here */ }

    // Derived classes can override the base class implementation.
    public virtual int Drive(int miles, int speed) { /* Method statements here */ return 1; }

    // Derived classes must implement this.
    public abstract double GetTopSpeed(); 
}


메서드 액세스

1. 개체에 대한 메서드 호출은 필드 액세스와 비슷합니다.

2. 개체 이름 뒤에 마침표, 메서드 이름 및 괄호를 추가합니다.

3. 인수는 괄호 안에 나열되고 쉼표로 구분합니다.

4. Motorcycle 클래스의 메서드는 다음 예제와 같이 호출될 수 있습니다.


class TestMotorcycle : Motorcycle
{

    public override double GetTopSpeed()
    {
        return 108.4;
    }

    static void Main()
    {

        TestMotorcycle moto = new TestMotorcycle();

        moto.StartEngine();
        moto.AddGas(15);
        moto.Drive(5, 20);
        double speed = moto.GetTopSpeed();
        Console.WriteLine("My top speed is {0}", speed);            
    }
}


메서드 매개 변수 및 인수

1. 메서드 정의는 필요한 모든 매개 변수의 이름 및 형식을 지정합니다.

2. 호출하는 코드에서 메서드를 호출할 때 해당 코드는 각 매개 변수에 대한 인수라는 구체적인 값을 제공합니다.

3. 인수는 매개 변수 형식과 호환되어야 하지만 호출하는 코드에 사용된 인수 이름(있는 경우)은 메서드에 정의된 명명된 매개 변수와 동일할 필요가 없습니다. 예:


public void Caller()
{
    int numA = 4;
    // Call with an int variable.
    int productA = Square(numA);

    int numB = 32;
    // Call with another int variable.
    int productB = Square(numB);

    // Call with an integer literal.
    int productC = Square(12);

    // Call with an expression that evaulates to int.
    productC = Square(productA * 3);
}

int Square(int i)
{
    // Store input argument in a local variable.
    int input = i;
    return input * input;
}


참조로 전달 및 값으로 전달

1. 기본적으로 값 형식이 메서드에 전달될 때 개체 자체가 아닌 복사본이 전달됩니다.

2. 따라서 인수에 대한 변경 내용은 호출하는 메서드의 원래 복사본에 영향을 주지 않습니다.

3. ref 키워드를 사용하여 값 형식을 참조로 전달할 수 있습니다.

4. 자세한 내용은 값 형식 매개 변수 전달(C# 프로그래밍 가이드)을 참조하세요.

5. 기본 제공 값 형식의 목록은 값 형식 표(C# 참조)를 참조하세요.

6. 참조 형식의 개체가 메서드에 전달될 때 개체에 대한 참조가 전달됩니다.

7. 즉, 메서드는 개체 자체가 아니라 개체의 위치를 나타내는 인수를 수신합니다.

8. 이 참조를 사용하여 개체의 멤버를 변경하면 개체를 값으로 전달하더라도 변경 내용은 호출하는 메서드의 인수에 반영됩니다.

9. 다음 예제와 같이 class 키워드를 사용하여 참조 형식을 만듭니다.


public class SampleRefType
{
    public int value;
}


10. 이제 이 형식에 기반을 둔 개체를 메서드에 전달하면 개체에 대한 참조가 전달됩니다.

11. 다음 예제에서는 SampleRefType 형식의 개체를 ModifyObject 메서드에 전달합니다.


public static void TestRefType()
{
    SampleRefType rt = new SampleRefType();
    rt.value = 44;
    ModifyObject(rt);
    Console.WriteLine(rt.value);
}
static void ModifyObject(SampleRefType obj)
{
    obj.value = 33;
}


12. 이 예제는 인수를 값으로 메서드에 전달한다는 점에서 기본적으로 이전 예제와 같은 작업을 수행합니다.

13. 그러나 참조 형식이 사용되므로 결과가 다릅니다.

14. ModifyObject에서 매개 변수 obj의 value 필드에 대해 수정한 내용으로 인해 TestRefType 메서드에서 rt 인수의 value 필드도 변경됩니다.

15. TestRefType 메서드는 출력으로 33을 표시합니다.

16. 참조 형식을 참조 및 값으로 전달하는 방법에 대한 자세한 내용은 참조 형식 매개 변수 전달(C# 프로그래밍 가이드) 및 참조 형식(C# 참조)을 참조하세요.




매개 변수전달


1. C#에서는 인수를 값이나 참조로 매개 변수에 전달할 수 있습니다.

2. 참조로 전달하면 함수 멤버, 메서드, 속성, 인덱서, 연산자 및 생성자에서 매개 변수 값을 변경하고 그 변경 내용을 호출 환경에서 유지할 수 있습니다.

3. 참조로 매개 변수를 전달하려면 ref 또는 out 키워드를 사용하는데, 편의상 이 항목의 예제에서는 ref 키워드만 사용합니다.

4. ref 및 out의 차이점에 대한 자세한 내용은 ref(C# 참조), out(C# 참조) 및 ref 및 out을 사용하여 배열 전달(C# 프로그래밍 가이드)을 참조하십시오.

5. 다음 예제에서는 값과 참조 매개 변수 간의 차이점을 보여 줍니다.


class Program
{
    static void Main(string[] args)
    {
        int arg;

        // Passing by value.
        // The value of arg in Main is not changed.
        arg = 4;
        squareVal(arg);
        Console.WriteLine(arg);
        // Output: 4

        // Passing by reference.
        // The value of arg in Main is changed.
        arg = 4;
        squareRef(ref arg);
        Console.WriteLine(arg);
        // Output: 16 
    }

    static void squareVal(int valParameter)
    {
        valParameter *= valParameter;
    }

    // Passing by reference
    static void squareRef(ref int refParameter)
    {
        refParameter *= refParameter;
    }
}



값 형식 매개 변수 전달

1. 값 형식 변수는 데이터에 대한 참조를 포함하는 참조 형식 변수와는 반대로 데이터를 직접 포함합니다.

2. 메서드에 값-형식 변수를 값으로 전달 변수의 복사본을 전달 하는 것을 의미 합니다.

3. 메서드에서 일어나는 매개 변수에 대한 변화는 인수 변수에 저장된 원본 데이터에 영향을 주지 않습니다.

4. 호출된 된 메서드의 매개 변수 값을 변경 하려면 참조로 전달 해야 합니다. ref 또는 out 키워드 이용.

5. 편의상 다음 예제에서는 ref를 사용합니다.


값으로 값 형식 전달

1. 다음 예제에서는 값으로 값-형식 매개 변수를 전달하는 것에 대해 설명합니다.

2. 변수 n는 값으로 메서드 SquareIt에 전달됩니다.

3. 메서드 안에서의 변경 사항은 변수의 원래 값에 영향을 주지 않습니다.


class PassingValByVal
{
    static void SquareIt(int x)
    // The parameter x is passed by value.
    // Changes to x will not affect the original value of x.
    {
        x *= x;
        System.Console.WriteLine("The value inside the method: {0}", x);
    }
    static void Main()
    {
        int n = 5;
        System.Console.WriteLine("The value before calling the method: {0}", n);

        SquareIt(n);  // Passing the variable by value.
        System.Console.WriteLine("The value after calling the method: {0}", n);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 5
*/


4. 변수 n 는 값 형식입니다.

5. 값 데이터를 포함 5. SquareIt를 호출하면 n의 내용은 매개 변수 x에 복사되어 메서드에서 제곱됩니다.

6. 그러나 Main에서 SquareIt 메서드를 호출한 후의 n의 값은 값은 전과 같습니다.

7. 메서드 내에 발생 하는 변경은 지역 변수에만 영향을 미칩니다.


참조로 참조 형식 전달

1. ref 매개 변수로 전달을 제외하고는 이전 예제와 동일합니다.

2. 메서드에서 x의 변경될 때 기본 인수 값 n도 바뀝니다.


class PassingValByRef
{
    static void SquareIt(ref int x)
    // The parameter x is passed by reference.
    // Changes to x will affect the original value of x.
    {
        x *= x;
        System.Console.WriteLine("The value inside the method: {0}", x);
    }
    static void Main()
    {
        int n = 5;
        System.Console.WriteLine("The value before calling the method: {0}", n);

        SquareIt(ref n);  // Passing the variable by reference.
        System.Console.WriteLine("The value after calling the method: {0}", n);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 25
*/


3. 이 예제에서 전달되는 것은 n의 값이 아니라 n에 대한 참조입니다.

4. 매개 변수 x는 int가 아니라 int에 대한 참조(이 경우에는 n에 대한 참조)입니다.

5. 따라서, x 제곱 메서드 내에서 실제로 제곱 되 란 x 참조 n.


값 형식 맞바꾸기

1. 인수 값을 변경 하는 일반적인 예는 스왑 메서드입니다. 스왑 메서드는 두 변수를 메서드에 전달 하고 메서드 내용을 맞바꿉니다.

3. Swap 메서드에 참조로 인수를 전달해야 합니다.

4. 그렇지 않으면 메서드 안에서 매개 변수의 로컬 복사본을 바꾸게 되고, 호출 메서드에서는 변화는 없습니다.

5. 다음 예제에서는 정수 값을 바꿉니다.


static void SwapByRef(ref int x, ref int y)
{
    int temp = x;
    x = y;
    y = temp;
}


6. SwapByRef 메서드를 호출할 때 다음의 예처럼 ref 키워드를 사용합니다.


static void Main()
{
    int i = 2, j = 3;
    System.Console.WriteLine("i = {0}  j = {1}" , i, j);

    SwapByRef (ref i, ref j);

    System.Console.WriteLine("i = {0}  j = {1}" , i, j);

    // Keep the console window open in debug mode.
    System.Console.WriteLine("Press any key to exit.");
    System.Console.ReadKey();
}
/* Output:
    i = 2  j = 3
    i = 3  j = 2
*/



참조 형식 매개 변수 전달

1. 참조 형식 변수는 데이터를 직접 포함하지 않고 데이터에 대한 참조를 포함합니다.

2. 값으로 참조-형식 매개 변수를 전달할 경우 클래스 멤버 값과 같은 참조에서 가리키는 데이터를 변경할 수 있으나 참조 값 자체를 변경할 수는 없습니다.

3. 즉, 같은 참조를 사용하여 새 클래스에 메모리를 할당하고 이를 블록 밖에서 지속하도록 할 수 없습니다.

4. 그렇게 하려면 ref 또는 out 키워드를 사용하여 매개 변수를 전달해야 합니다.

5. 편의상 다음 예제에서는 ref를 사용합니다.


값으로 참조 형식 전달

1. 다음 예제에서는 참조-형식 매개 변수 arr를 값으로 Change 메서드에 전달하는 것에 대해 설명합니다.

2. 매개 변수가 arr에 대한 참조이므로 배열 요소 값을 변경할 수 있습니다.

3. 그러나 다른 메모리 위치에 매개 변수를 다시 할당하는 것은 메서드 안에서만 가능하며 원래 변수 arr에 영향을 주지는 않습니다.


class PassingRefByVal 
{
    static void Change(int[] pArray)
    {
        pArray[0] = 888;  // This change affects the original element.
        pArray = new int[5] {-3, -1, -2, -3, -4};   // This change is local.
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }

    static void Main() 
    {
        int[] arr = {1, 4, 5};
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr [0]);

        Change(arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr [0]);
    }
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: 888
*/


4. 이전 예제에서 참조 형식인 arr 배열은 ref 매개 변수 없이 메서드에 전달됩니다.

5. 이러한 경우 arr를 가리키는 참조의 복사본이 메서드로 전달됩니다.

6. 출력은 메서드로 배열 요소 내용(이 경우 1부터 888까지)을 변경할 수 있음을 보여 줍니다.

7. 그러나, Change 메서드 안에서 new 연산자를 사용하여 메모리 일부를 새로 할당하면 변수 pArray은 새 배열을 참조하게 됩니다.

8. 따라서 이후의 모든 변경 사항은 Main에서 만들어진 원본 배열 arr에 영향을 주지 않습니다.

9. 실제로 이 예제에서 두 개의 배열이 만들어지는데, 하나는 Main 안에서 다른 하나는 Change 메서드 안에서 만들어집니다.


참조로 참조 형식 전달

1. 다음 예제에서는 제외 하 고 이전 예제와 동일은 ref 키워드는 메서드 헤더와 호출에 추가 됩니다.

2. 메서드에서 변수 변경 호출 프로그램에서 원래 변수에 영향을 줍니다.


class PassingRefByRef 
{
    static void Change(ref int[] pArray)
    {
        // Both of the following changes will affect the original variables:
        pArray[0] = 888;
        pArray = new int[5] {-3, -1, -2, -3, -4};
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }

    static void Main() 
    {
        int[] arr = {1, 4, 5};
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

        Change(ref arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
    }
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: -3
*/


3. 메서드에서의 모든 변경 사항은 Main의 원본 배열에 영향을 줍니다.

4. 실제로 new 연산자를 사용하여 원본 배열을 다시 할당합니다.

5. 따라서, Change 메서드를 호출한 후 arr에 대한 모든 참조는 Change 메서드에서 만들어진 다섯 요소 배열을 가리킵니다.


두 문자열 맞바꾸기

1. 문자열 맞바꾸기는 참조로 참조-형식 매개 변수를 전달하는 좋은 예입니다.

2. 예제에서 두 문자열 str1과 str2는 Main에서 초기화되고 ref 키워드에 의해 한정된 매개 변수로 SwapStrings 메서드에 전달됩니다.

3. 두 문자열은 메서드와 Main에서 맞바뀝니다.


 class SwappingStrings
 {
     static void SwapStrings(ref string s1, ref string s2)
     // The string parameter is passed by reference.
     // Any changes on parameters will affect the original variables.
     {
         string temp = s1;
         s1 = s2;
         s2 = temp;
         System.Console.WriteLine("Inside the method: {0} {1}", s1, s2);
     }

     static void Main()
     {
         string str1 = "John";
         string str2 = "Smith";
         System.Console.WriteLine("Inside Main, before swapping: {0} {1}", str1, str2);

         SwapStrings(ref str1, ref str2);   // Passing strings by reference
         System.Console.WriteLine("Inside Main, after swapping: {0} {1}", str1, str2);
     }
 }
 /* Output:
     Inside Main, before swapping: John Smith
     Inside the method: Smith John
     Inside Main, after swapping: Smith John
*/


4. 이 예제에서 호출 프로그램의 변수에 영향을 주려면 참조로 매개 변수를 전달해야 합니다.

5. 메서드 헤더와 메서드 호출 모두에서 ref 키워드를 제거하면 호출 프로그램에서 아무것도 변경되지 않습니다.

6.문자열에 대한 자세한 내용은 문자열을 참조하십시오.


방법: 메서드에 대한 구조체 전달과 클래스 참조 전달 간의 차이점 이해

1. The following example demonstrates how passing a struct to a method differs from passing a class instance to a method.

2. In the example, both of the arguments (struct and class instance) are passed by value, and both methods change the value of one field of the argument.

3. However, the results of the two methods are not the same because what is passed when you pass a struct differs from what is passed when you pass an instance of a class.

4. Because a struct is a value type, when you pass a struct by value to a method, the method receives and operates on a copy of the struct argument.

5. The method has no access to the original struct in the calling method and therefore can't change it in any way. The method can change only the copy.

6. A class instance is a reference type, not a value type.

7. When a reference type is passed by value to a method, the method receives a copy of the reference to the class instance.

8. That is, the method receives a copy of the address of the instance, not a copy of the instance itself.

9. The class instance in the calling method has an address, the parameter in the called method has a copy of the address, and both addresses refer to the same object.

10. Because the parameter contains only a copy of the address, the called method cannot change the address of the class instance in the calling method.

11. However, the called method can use the address to access the class members that both the original address and the copy reference.

12. If the called method changes a class member, the original class instance in the calling method also changes.

13. The output of the following example illustrates the difference. The value of the willIChange field of the class instance is changed by the call to method ClassTaker because the method uses the address in the parameter to find the specified field of the class instance.

14. The willIChange field of the struct in the calling method is not changed by the call to method StructTaker because the value of the argument is a copy of the struct itself, not a copy of its address.

15. StructTaker changes the copy, and the copy is lost when the call to StructTaker is completed.


예제

class TheClass
{
    public string willIChange;
}

struct TheStruct
{
    public string willIChange;
}

class TestClassAndStruct
{
    static void ClassTaker(TheClass c)
    {
        c.willIChange = "Changed";
    }

    static void StructTaker(TheStruct s)
    {
        s.willIChange = "Changed";
    }

    static void Main()
    {
        TheClass testClass = new TheClass();
        TheStruct testStruct = new TheStruct();

        testClass.willIChange = "Not Changed";
        testStruct.willIChange = "Not Changed";

        ClassTaker(testClass);
        StructTaker(testStruct);

        Console.WriteLine("Class field = {0}", testClass.willIChange);
        Console.WriteLine("Struct field = {0}", testStruct.willIChange);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Class field = Changed
    Struct field = Not Changed
*/




암시적으로 형식화한 지역 변수


1. 지역 변수에 명시적 형식 대신 유추된 var "형식"을 지정할 수 있습니다.

2. var 키워드는 초기화 문의 오른쪽에 있는 식에서 변수 형식을 유추하도록 컴파일러에 지시합니다.

3. 유추된 형식은 기본 제공 형식, 익명 형식, 사용자 정의 형식 또는 .NET Framework 클래스 라이브러리에서 정의된 형식일 수 있습니다.

4. var을 사용하여 배열을 초기화하는 방법에 대한 자세한 내용은 암시적으로 형식화된 배열(C# 프로그래밍 가이드)을 참조하십시오.

5. 다음 예제에서는 var을 사용하여 지역 변수를 선언할 수 있는 다양한 방법을 보여 줍니다.


// i is compiled as an int
var i = 5;

// s is compiled as a string
var s = "Hello";

// a is compiled as int[]
var a = new[] { 0, 1, 2 };

// expr is compiled as IEnumerable<Customer>
// or perhaps IQueryable<Customer>
var expr =
    from c in customers
    where c.City == "London"
    select c;

// anon is compiled as an anonymous type
var anon = new { Name = "Terry", Age = 34 };

// list is compiled as List<int>                             
var list = new List<int>();


6. var 키워드는 "variant"를 의미하지 않으며 변수가 느슨한 형식이거나 런타임에 바인딩됨을 나타내지 않습니다.

7. 이 키워드는 단지 컴파일러에서 가장 적합한 형식을 결정하여 할당한다는 것을 의미합니다.

8. var 키워드는 다음 컨텍스트에서 사용할 수 있습니다.

1) 앞의 예제에서 표시된 것처럼 지역 변수(메서드 범위에서 선언된 변수)에서

2) for 초기화 문에서


for(var x = 1; x < 10; x++)


3) foreach 초기화 문에서


foreach(var item in list){...}


4) using 문에서


using (var file = new StreamReader("C:\\myfile.txt")) {...}


9. 자세한 내용은 방법: 쿼리 식에서 암시적으로 형식화된 지역 변수 및 배열 사용(C# 프로그래밍 가이드)를 참조하십시오.


var 및 익명 형식

1. 대부분의 경우 var 사용은 선택 사항이며 단지 구문상 편의를 위해 사용됩니다.

2. 하지만 변수가 익명 형식으로 초기화되는 경우 나중에 개체의 속성에 액세스해야 하면 변수를 var로 선언해야 합니다.

3. 이는 LINQ 쿼리 식의 일반적인 시나리오입니다.

4. 자세한 내용은 익명 형식(C# 프로그래밍 가이드)을 참조하십시오.

5. 소스 코드의 관점에서 보면 익명 형식에는 이름이 없습니다.

6. 따라서 쿼리 변수가 var로 초기화된 경우 반환된 개체 시퀀스에서 속성에 액세스하는 유일한 방법은 foreach 문에서 반복 변수의 형식으로 var을 사용하는 것입니다.


class ImplicitlyTypedLocals2
{
    static void Main()
    {
        string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };

        // If a query produces a sequence of anonymous types, 
        // then use var in the foreach statement to access the properties.
        var upperLowerWords =
             from w in words
             select new { Upper = w.ToUpper(), Lower = w.ToLower() };

        // Execute the query
        foreach (var ul in upperLowerWords)
        {
            Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper, ul.Lower);
        }
    }
}
/* Outputs:
    Uppercase: APPLE, Lowercase: apple
    Uppercase: BLUEBERRY, Lowercase: blueberry
    Uppercase: CHERRY, Lowercase: cherry        
 */


설명

1. 암시적으로 형식화된 선언에는 다음과 같은 제한 사항이 있습니다.

1) var은 동일한 문에서 지역 변수를 선언하고 초기화하는 경우에만 사용할 수 있습니다. 변수를 null, 메서드 그룹 또는 익명 함수로 초기화할 수 없습니다.

2) var은 클래스 범위의 필드에서 사용할 수 없습니다.

3) var을 사용하여 선언된 변수는 초기화 식에서 사용할 수 없습니다. 즉 : int i = (i = 20); 식은 문제가 없지만 이 식에서는 컴파일 타임 오류인 var i = (i = 20);가 발생합니다.

4) 암시적으로 형식화된 여러 변수를 동일한 문에서 초기화할 수 없습니다.

5) var이라는 이름의 형식이 범위에 있으면 var 키워드는 해당 형식 이름으로 확인되며 암시적으로 형식화된 지역 변수 선언의 일부로 처리되지 않습니다.

2. var은 쿼리 변수의 생성된 형식을 정확하게 확인하기 어려운 쿼리 식에서 유용할 수도 있습니다.

3. 그룹화 및 정렬 작업에서 이러한 경우가 발생할 수 있습니다.

4. var 키워드는 특정 변수 형식이 키보드에서 형식화하기 번거롭거나 명백하거나 코드 가독성에 도움이 되지 않는 경우에도 유용할 수 있습니다.

5. var이 이런 방식으로 유용한 한 가지 예제로 그룹 작업에 사용되는 형식과 같은 중첩 제네릭 형식이 있습니다.

6. 다음 쿼리에서 쿼리 변수의 형식은 IEnumerable<IGrouping<string, Student>>입니다.

7. 사용자 및 코드를 유지 관리해야 하는 다른 사용자를 이를 이해하기만 하면 간편하게 암시적 형식화를 사용하는 데 아무 문제도 없습니다.


// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
 var studentQuery3 =
     from student in students
     group student by student.Last;


그러나 var을 사용하면 다른 개발자가 코드를 이해하는 것이 더 어려워질 수 있습니다. 이런 이유로 C# 문서에서는 일반적으로 필요한 경우에만 var을 사용합니다.


방법: 쿼리 식에서 암시적으로 형식화된 지역 변수 및 배열 사용

1. 컴파일러가 지역 변수의 형식을 확인하도록 하려고 할 때마다 암시적으로 형식화된 지역 변수를 사용할 수 있습니다.

2. 쿼리 식에서 자주 사용되는 익명 형식을 저장하려면 암시적으로 형식화된 지역 변수를 사용해야 합니다.

3. 다음 예제에서는 암시적으로 형식화된 지역 변수를 쿼리에 선택적으로 사용할 수 있는 경우와 필수적으로 사용해야 하는 경우를 모두 보여 줍니다.

4. 암시적으로 형식화된 지역 변수는 var 컨텍스트 키워드를 사용하여 선언됩니다.

5. 자세한 내용은 암시적으로 형식화된 지역 변수(C# 프로그래밍 가이드) 및 암시적으로 형식화된 배열(C# 프로그래밍 가이드)을 참조하십시오.


예제

1. 다음 예제에서는 var 키워드가 필요한 일반적인 시나리오 즉, 익명 형식의 시퀀스를 생성하는 쿼리 식을 보여 줍니다.

2. 이 시나리오에서는 사용자에게 익명 형식의 형식 이름에 대한 액세스 권한이 없기 때문에 쿼리 변수와 foreach 문의 반복 변수 모두 var를 사용하여 암시적으로 형식화되어야 합니다.

3. 익명 형식에 대한 자세한 내용은 익명 형식(C# 프로그래밍 가이드)을 참조하십시오.


private static void QueryNames(char firstLetter)
{
    // Create the query. Use of var is required because
    // the query produces a sequence of anonymous types:
    // System.Collections.Generic.IEnumerable<????>.
    var studentQuery =
        from student in students
        where student.FirstName[0] == firstLetter
        select new { student.FirstName, student.LastName };

    // Execute the query and display the results.
    foreach (var anonType in studentQuery)
    {
        Console.WriteLine("First = {0}, Last = {1}", anonType.FirstName, anonType.LastName);
    }
}


4. 다음 예제에서는 비슷한 상황이지만 var 사용이 선택적인 상황에서 var 키워드가 사용됩니다.

5. student.LastName이 문자열이므로 쿼리를 실행하면 문자열의 시퀀스가 반환됩니다.

6. 따라서 queryID의 형식을 var 대신 System.Collections.Generic.IEnumerable<string>으로 선언할 수 있습니다.

7. var 키워드는 편의를 위해 사용됩니다.

8. 예제에서 foreach 문의 반복 변수는 명시적으로 문자열로 형식화되지만 대신 var를 사용하여 선언될 수도 있습니다.

9. 반복 변수의 형식은 익명 형식이 아니기 때문에 var 사용은 필수가 아닌 선택입니다.

10. var 자체는 형식이 아니라 컴파일러가 형식을 유추하고 할당하도록 지정하는 명령입니다.


// Variable queryID could be declared by using 
// System.Collections.Generic.IEnumerable<string>
// instead of var.
var queryID =
    from student in students
    where student.ID > 111
    select student.LastName;

// Variable str could be declared by using var instead of string.     
foreach (string str in queryID)
{
    Console.WriteLine("Last name: {0}", str);
}




확장 메서드


1. 확장 메서드를 사용하면 새 파생 형식을 만들거나 다시 컴파일하거나 원래 형식을 수정하지 않고도 기존 형식에 메서드를 "추가"할 수 있습니다.

2. 확장 메서드는 특수한 종류의 정적 메서드이지만 확장 형식의 인스턴스 메서드인 것처럼 호출됩니다.

3. C# 및 Visual Basic에서 작성된 클라이언트 코드의 경우 확장 메서드를 호출하는 것과 형식에 실제로 정의된 메서드를 호출하는 데는 명백한 차이가 없습니다.

4. 가장 일반적인 확장 메서드는 쿼리 기능을 기존 System.Collections.IEnumerable 및 System.Collections.Generic.IEnumerable<T> 형식에 추가하는 LINQ 표준 쿼리 연산자입니다.

5. 표준 쿼리 연산자를 사용하려면 using System.Linq 지시문을 사용해서 먼저 범위를 지정합니다.

6. 그러면 IEnumerable<T>을 구현하는 모든 형식에 GroupBy<TSource, TKey>, OrderBy<TSource, TKey>, Average 등의 인스턴스 메서드가 있는 것처럼 나타납니다.

7. List<T> 또는 Array와 같은 IEnumerable<T> 형식의 인스턴스 뒤에 "dot"를 입력하면 IntelliSense 문 완성에서 이러한 추가 메서드를 볼 수 있습니다.

8. 다음 예제에서는 정수 배열에서 표준 쿼리 연산자 OrderBy를 호출하는 방법을 보여 줍니다.

9. 괄호 안의 식은 람다 식입니다. 많은 표준 쿼리 연산자가 람다 식을 매개 변수로 사용하지만 확장 메서드에 대한 요구 사항은 아닙니다. 자세한 내용은 람다 식(C# 프로그래밍 가이드)을 참조하십시오.


class ExtensionMethods2    
{

    static void Main()
    {            
        int[] ints = { 10, 45, 15, 39, 21, 26 };
        var result = ints.OrderBy(g => g);
        foreach (var i in result)
        {
            System.Console.Write(i + " ");
        }           
    }        
}
//Output: 10 15 21 26 39 45


10. 확장 메서드는 정적 메서드로 정의되지만 인스턴스 메서드 구문을 사용하여 호출됩니다.

11. 확장 메서드의 첫 번째 매개 변수는 메서드가 작동하는 형식을 지정하며 매개 변수 앞에 this 한정자가 있습니다.

12. 확장 메서드는 using 지시문을 사용하여 명시적으로 네임스페이스를 소스 코드로 가져오는 경우에만 범위에 있습니다.

13. 다음 예제에서는 System.String 클래스에 대해 정의된 확장 메서드를 보여 줍니다. 이 확장 메서드는 제네릭이 아닌 비중첩 정적 클래스 내부에서 정의됩니다.


namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this String str)
        {
            return str.Split(new char[] { ' ', '.', '?' }, 
                             StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }   
}


14. using 지시문을 사용하여 WordCount 확장 메서드를 범위로 가져올 수 있습니다.


using ExtensionMethods;


15. 또한 다음 구문을 사용하여 응용 프로그램에서 확장 메서드를 호출할 수 있습니다.


string s = "Hello Extension Methods";
int i = s.WordCount();


16. 코드에서 인스턴스 메서드 구문을 사용하여 확장 메서드를 호출합니다.

17. 그러나 컴파일러에서 생성된 IL(중간 언어)이 코드를 정적 메서드 호출로 변환합니다.

18. 따라서 실제로 캡슐화의 원칙을 위반하지 않습니다.

19. 사실상 확장 메서드는 확장하는 형식의 private 변수에 액세스할 수 없습니다.

20. 자세한 내용은 방법: 사용자 지정 확장 메서드 구현 및 호출(C# 프로그래밍 가이드)을 참조하십시오.

21. 일반적으로 확장 메서드를 직접 구현하는 것보다 호출하는 경우가 훨씬 많습니다.

22. 확장 메서드는 인스턴스 메서드 구문을 사용하여 호출되므로 특별한 지식이 없어도 클라이언트 코드에서 확장 메서드를 사용할 수 있습니다.

23. 특정 형식의 확장 메서드를 사용하려면 해당 메서드가 정의된 네임스페이스에 대해 using 지시문을 추가합니다. 24. 예를 들어 표준 쿼리 연산자를 사용하려면 다음 using 지시문을 코드에 추가합니다.


using System.Linq;


25. System.Core.dll에 대한 참조를 추가해야 할 수도 있습니다.

26. 이제 표준 쿼리 연산자가 대부분의 IEnumerable<T> 형식에 사용할 수 있는 추가 메서드로 IntelliSense에 표시됩니다.


참고

String에 대한 표준 쿼리 연산자는 IntelliSense에는 표시되지 않지만 사용할 수 있습니다.


컴파일 타임에 확장 메서드 바인딩

확장 메서드를 사용하여 클래스 또는 인터페이스를 확장할 수 있지만 재정의할 수는 없습니다. 이름과 시그니처가 인터페이스 또는 클래스 메서드와 동일한 확장 메서드는 호출되지 않습니다. 컴파일 타임에 확장 메서드는 항상 형식 자체에서 정의된 인스턴스 메서드보다 우선 순위가 낮습니다. 즉, 형식에 Process(int i)라는 메서드가 있고 동일한 시그니처를 가진 확장 메서드가 있는 경우 컴파일러는 항상 인스턴스 메서드에 바인딩합니다. 컴파일러는 메서드 호출을 발견할 경우 먼저 형식의 인스턴스 메서드에서 일치 항목을 찾습니다. 일치 항목이 없으면 형식에 대해 정의된 확장 메서드를 검색하고 찾은 첫 번째 확장 메서드에 바인딩합니다. 다음 예제에서는 컴파일러가 바인딩할 확장 메서드 또는 인스턴스 메서드를 확인하는 방법을 보여 줍니다.


예제

1. 다음 예제에서는 C# 컴파일러가 메서드 호출을 형식의 인스턴스 메서드 또는 확장 메서드에 바인딩할 것인지 결정할 때 따르는 규칙을 보여 줍니다.

2. 정적 클래스 Extensions는 IMyInterface를 구현하는 모든 형식에 대해 정의된 확장 메서드를 포함합니다.

3. A, B 및 C 클래스는 모두 인터페이스를 구현합니다.

4. MethodB 확장 메서드는 이름과 시그니처가 클래스에서 이미 구현된 메서드와 정확하게 일치하므로 호출되지 않습니다.

5. 일치하는 시그니처를 가진 인스턴스 메서드를 찾을 수 없으면 컴파일러는 일치하는 확장 메서드(있는 경우)에 바인딩합니다.


// Define an interface named IMyInterface.
namespace DefineIMyInterface
{
    using System;

    public interface IMyInterface
    {
        // Any class that implements IMyInterface must define a method
        // that matches the following signature.
        void MethodB();
    }
}


// Define extension methods for IMyInterface.
namespace Extensions
{
    using System;
    using DefineIMyInterface;

    // The following extension methods can be accessed by instances of any 
    // class that implements IMyInterface.
    public static class Extension
    {
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, int i)");
        }

        public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, string s)");
        }

        // This method is never called in ExtensionMethodsDemo1, because each 
        // of the three classes A, B, and C implements a method named MethodB
        // that has a matching signature.
        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine
                ("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}


// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;
    using DefineIMyInterface;

    class A : IMyInterface
    {
        public void MethodB() { Console.WriteLine("A.MethodB()"); }
    }

    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }

    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }
        public void MethodA(object obj)
        {
            Console.WriteLine("C.MethodA(object obj)");
        }
    }

    class ExtMethodDemo
    {
        static void Main(string[] args)
        {
            // Declare an instance of class A, class B, and class C.
            A a = new A();
            B b = new B();
            C c = new C();

            // For a, b, and c, call the following methods:
            //      -- MethodA with an int argument
            //      -- MethodA with a string argument
            //      -- MethodB with no argument.

            // A contains no MethodA, so each call to MethodA resolves to 
            // the extension method that has a matching signature.
            a.MethodA(1);           // Extension.MethodA(object, int)
            a.MethodA("hello");     // Extension.MethodA(object, string)

            // A has a method that matches the signature of the following call
            // to MethodB.
            a.MethodB();            // A.MethodB()

            // B has methods that match the signatures of the following
            // method calls.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB()

            // B has no matching method for the following call, but 
            // class Extension does.
            b.MethodA("hello");     // Extension.MethodA(object, string)

            // C contains an instance method that matches each of the following
            // method calls.
            c.MethodA(1);           // C.MethodA(object)
            c.MethodA("hello");     // C.MethodA(object)
            c.MethodB();            // C.MethodB()
        }
    }
}
/* Output:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    B.MethodB()
    Extension.MethodA(this IMyInterface myInterface, string s)
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
 */


일반 지침

1. 일반적으로 반드시 필요한 경우에만 드물게 확장 메서드를 구현하는 것이 좋습니다.

2. 가능하면 기존 형식을 확장해야 하는 클라이언트 코드는 기존 형식에서 파생된 새 형식을 만들어 이 작업을 수행해야 합니다.

3. 자세한 내용은 상속(C# 프로그래밍 가이드)을 참조하십시오.

4. 기존 메서드를 사용하여 소스 코드를 변경할 수 없는 형식을 확장하는 경우 형식의 구현이 변경되어 확장 메서드가 손상될 수도 있습니다.

5. 지정된 형식에 대해 확장 메서드를 구현하는 경우 다음 사항을 명심하십시오.

6. 시그니처가 형식에 정의된 메서드와 동일한 확장 메서드는 호출되지 않습니다.

7. 확장 메서드는 네임스페이스 수준에서 범위로 가져옵니다.

8. 예를 들어 Extensions라는 단일 네임스페이스에 확장 메서드를 포함하는 여러 개의 정적 클래스가 있는 경우 using Extensions; 지시문을 통해 모두 범위로 가져옵니다.

9. 구현된 클래스 라이브러리의 경우 어셈블리의 버전 번호가 증가되는 것을 방지하기 위해 확장 메서드를 사용해서는 안 됩니다.

10. 소스 코드를 소유하고 있는 라이브러리에 중요 기능을 추가하려는 경우 어셈블리 버전 관리를 위한 표준 .NET Framework 지침을 따라야 합니다.

11. 자세한 내용은 어셈블리 버전 관리을 참조하십시오.


방법: 사용자 지정 확장 메서드 구현 및 호출

1. 이 항목에서는 자신의 확장 메서드를 구현 하는 방법을 보여 줍니다. (.NET Framework Class Library의 형식 또는 확장하고자 하는 다른.NET 형식)

2. 클라이언트 코드는 확장 메서드를 포함하는 DLL에 대한 참조를 추가하고 확장 메서드가 정의된 네임스페이스를 지정하는 using 지시문을 추가하여 확장 메서드를 사용할 수 있습니다.


확장 메서드를 정의하고 호출하려면

1. 확장 메서드를 포함하는 정적 클래스를 정의합니다. 클래스가 클라이언트 코드에 표시되어야 합니다. 액세스 가능성 규칙에 대한 자세한 내용은 액세스 한정자(C# 프로그래밍 가이드)를 참조하십시오.

2. 표시 유형이 적어도 포함하는 클래스와 동일한 정적 메서드로 확장 메서드를 구현합니다.

3. 메서드의 첫 번째 매개 변수는 메서드가 작동하는 형식을 지정하며 앞에 this 한정자가 있어야 합니다.

4. 호출 코드에서 using 지시문을 추가하여 확장 메서드 클래스를 포함하는 네임스페이스를 지정합니다.

5. 형식의 인스턴스 메서드인 것처럼 메서드를 호출합니다.

6. 첫 번째 매개 변수는 연산자가 적용되는 형식을 나타내며 컴파일러가 개체의 형식을 이미 알고 있으므로 호출 코드에서 지정되지 않습니다. 매개 변수 2에서 n까지에 대한 인수만 제공하면 됩니다.


예제

1. 다음 예제에서는 CustomExtensions.StringExtension 클래스에 WordCount라는 확장 메서드를 구현합니다.

2. 이 메서드는 첫 번째 메서드 매개 변수로 지정된 String 클래스에서 작동합니다.

3. CustomExtensions 네임스페이스를 응용 프로그램 네임스페이스로 가져오고 Main 메서드 내부에서 메서드가 호출됩니다.


using System.Linq;
using System.Text;
using System;

namespace CustomExtensions
{
    //Extension methods must be defined in a static class
    public static class StringExtension
    {
        // This is the extension method.
        // The first parameter takes the "this" modifier
        // and specifies the type for which the method is defined.
        public static int WordCount(this String str)
        {
            return str.Split(new char[] {' ', '.','?'}, StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}
namespace Extension_Methods_Simple
{
    //Import the extension method namespace.
    using CustomExtensions;
    class Program
    {
        static void Main(string[] args)
        {
            string s = "The quick brown fox jumped over the lazy dog.";
            //  Call the method as if it were an 
            //  instance method on the type. Note that the first
            //  parameter is not specified by the calling code.
            int i = s.WordCount();
            System.Console.WriteLine("Word count of s is {0}", i);
        }
    }
}


방법: 새 열거형 메서드 만들기

1. 확장 메서드를 사용하여 특정 열거형 형식에 특정한 기능을 추가할 수 있습니다.


예제

1. 다음 예제에서 Grades 열거형은 학생이 클래스에서 받을 수 있는 문자 등급을 나타냅니다.

2. Passing이라는 확장 메서드가 Grades 형식에 추가되어 이제 형식의 각 인스턴스는 통과 등급을 나타낼지 여부를 "알 수 있습니다".


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace EnumExtension
{
    // Define an extension method in a non-nested static class.
    public static class Extensions
    {        
        public static Grades minPassing = Grades.D;
        public static bool Passing(this Grades grade)
        {
            return grade >= minPassing;
        }
    }

    public enum Grades { F = 0, D=1, C=2, B=3, A=4 };
    class Program
    {       
        static void Main(string[] args)
        {
            Grades g1 = Grades.D;
            Grades g2 = Grades.F;
            Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
            Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");

            Extensions.minPassing = Grades.C;
            Console.WriteLine("\r\nRaising the bar!\r\n");
            Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
            Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");
        }
    }
  }
/* Output:
    First is a passing grade.
    Second is not a passing grade.

    Raising the bar!

    First is not a passing grade.
    Second is not a passing grade.
 */




명명된 인수 및 선택적 인수


1. Visual C# 2010에는 명명된 인수 및 선택적 인수가 도입되어 있습니다.

2. 명명된 인수를 사용하면 매개 변수 목록에서의 매개 변수 위치 대신 매개 변수의 이름과 인수를 연결하여 특정 매개 변수에 대한 인수를 지정할 수 있습니다.

3. 선택적 인수는 일부 매개 변수에 대한 인수를 생략할 수 있도록 합니다.

4. 이 두 가지 인수는 메서드, 인덱서, 생성자 및 대리자와 함께 사용할 수 있습니다.

5. 명명된 인수 및 선택적 인수를 사용할 경우 인수가 계산되는 순서는 매개 변수 목록이 아닌 인수 목록에서 인수가 나타나는 순서에 의해 결정됩니다.

6. 명명된 인수 및 선택적 인수가 함께 사용되면 선택적 매개 변수 목록의 일부 매개 변수에 대해서만 인수를 제공할 수 있습니다.

7. 이 기능 덕분에 Microsoft Office 자동화 API 같은 COM 인터페이스를 매우 쉽게 호출할 수 있습니다.


명명된 인수

1. 명명된 인수를 사용하면 호출되는 메서드의 매개 변수 목록에 있는 매개 변수의 순서를 기억하거나 확인할 필요가 없게 됩니다.

2. 각 인수에 대한 매개 변수를 지정하려면 매개 변수 이름을 사용하면 됩니다.

3. 예를 들어, BMI(체질량 지수)를 계산하는 함수의 경우 해당 함수에 정의된 순서에 따라 체중 및 높이에 대한 인수를 차례로 지정하여 전달하는 표준적인 방법으로 호출될 수 있습니다.

CalculateBMI(123, 64);

4. 매개 변수 순서를 기억하지 못하거나 매개 변수 이름을 모르면 체중 및 높이에 대한 인수를 순서와 상관없이 보낼 수 있습니다.

CalculateBMI(weight: 123, height: 64);

CalculateBMI(height: 64, weight: 123);

5. 명명된 인수를 사용하면 각 인수가 무엇을 나타내는지 확인할 수 있으므로 코드 가독성도 향상됩니다.

6. 명명된 인수는 다음과 같이 위치 인수 뒤에 올 수 있습니다.

CalculateBMI(123, height: 64);

7. 그러나 위치 인수가 명명된 인수 다음에 올 수는 없습니다. 다음 문은 컴파일러 오류를 발생시킵니다.

//CalculateBMI(weight: 123, 64);


예제

다음 코드에는 이 단원의 예제가 구현되어 있습니다.


class NamedExample
{
    static void Main(string[] args)
    {
        // The method can be called in the normal way, by using positional arguments.
        Console.WriteLine(CalculateBMI(123, 64));

        // Named arguments can be supplied for the parameters in either order.
        Console.WriteLine(CalculateBMI(weight: 123, height: 64));
        Console.WriteLine(CalculateBMI(height: 64, weight: 123));

        // Positional arguments cannot follow named arguments.
        // The following statement causes a compiler error.
        //Console.WriteLine(CalculateBMI(weight: 123, 64));

        // Named arguments can follow positional arguments.
        Console.WriteLine(CalculateBMI(123, height: 64));
    }

    static int CalculateBMI(int weight, int height)
    {
        return (weight * 703) / (height * height);
    }
}


선택적 인수

1. 메서드, 생성자, 인덱서 또는 대리자의 정의에서 해당 매개 변수가 필수적인지 선택적인지 지정할 수 있습니다.

2. 호출할 경우 모든 필수적 매개 변수에 대한 인수는 항상 제공해야 하지만 선택적 매개 변수에 대한 인수는 생략할 수 있습니다.

3. 각 선택적 매개 변수에는 정의의 일부로서 기본값이 있습니다.

4. 해당 매개 변수에 대해 인수를 전달하지 않으면 기본값이 사용됩니다.

5. 기본값 식의 다음 형식 중 하나 여야 합니다.

1) 상수 식입니다.

2) 폼의 식 new ValType(), 어디 ValType 값 형식에는 열거형 또는 구조체;

3) 폼의 식 default(ValType), 어디 ValType 는 값 형식입니다.

6. 선택적 매개 변수는 매개 변수 목록의 끝에, 즉 모든 필수적 매개 변수 뒤에 정의됩니다.

7. 호출자가 첫 번째 선택적 매개 변수 뒤에 있는 선택적 매개 변수에 대해 인수를 제공한 경우 이 매개 변수 앞에 있는 모든 선택적 매개 변수에 대해서도 인수를 제공해야 합니다.

8. 인수 목록에서 쉼표와 쉼표 사이에 공백만 남겨 두는 방법은 지원되지 않습니다.

9. 예를 들어, 다음 코드에서 ExampleMethod 인스턴스 메서드는 필수 매개 변수 1개와 선택적 매개 변수 2개를 갖도록 정의됩니다.


public void ExampleMethod(int required, string optionalstr = "default string",
    int optionalint = 10)


10. ExampleMethod에 대한 다음 코드에서는 두 번째 매개 변수가 아니라 세 번째 매개 변수에 인수가 제공되기 때문에 컴파일러 오류가 발생합니다.

//anExample.ExampleMethod(3, ,4);

11. 그러나 세 번째 매개 변수의 이름을 알고 있으면 명명된 인수를 사용하여 원하는 작업을 수행할 수 있습니다.

anExample.ExampleMethod(3, optionalint: 4);

12. 다음 그림과 같이 IntelliSense에서는 괄호를 사용하여 선택적 매개 변수를 표시합니다.

ExampleMethod의 선택적 매개 변수


참고

1. 선택적 매개 변수는 .NET OptionalAttribute 클래스를 사용하여 선언할 수도 있습니다.

2. OptionalAttribute 매개 변수에는 기본값이 필요하지 않습니다.


예제

1. 다음 예제에서 ExampleClass의 생성자에는 하나의 매개 변수가 있고 이 매개 변수는 선택적입니다.

2. 인스턴스 메서드 ExampleMethod에는 필수적 매개 변수로 required 하나가 있고, 선택적 매개 변수로 optionalstr 및 optionalint 두 개가 있습니다.

3. Main의 코드에서는 생성자 및 메서드를 호출할 수 있는 여러 방법을 보여 줍니다.


namespace OptionalNamespace
{
    class OptionalExample
    {
        static void Main(string[] args)
        {
            // Instance anExample does not send an argument for the constructor's
            // optional parameter.
            ExampleClass anExample = new ExampleClass();
            anExample.ExampleMethod(1, "One", 1);
            anExample.ExampleMethod(2, "Two");
            anExample.ExampleMethod(3);

            // Instance anotherExample sends an argument for the constructor's
            // optional parameter.
            ExampleClass anotherExample = new ExampleClass("Provided name");
            anotherExample.ExampleMethod(1, "One", 1);
            anotherExample.ExampleMethod(2, "Two");
            anotherExample.ExampleMethod(3);

            // The following statements produce compiler errors.

            // An argument must be supplied for the first parameter, and it
            // must be an integer.
            //anExample.ExampleMethod("One", 1);
            //anExample.ExampleMethod();

            // You cannot leave a gap in the provided arguments. 
            //anExample.ExampleMethod(3, ,4);
            //anExample.ExampleMethod(3, 4);

            // You can use a named parameter to make the previous 
            // statement work.
            anExample.ExampleMethod(3, optionalint: 4);
        }
    }

    class ExampleClass
    {
        private string _name;

        // Because the parameter for the constructor, name, has a default
        // value assigned to it, it is optional.
        public ExampleClass(string name = "Default name")
        {
            _name = name;
        }

        // The first parameter, required, has no default value assigned
        // to it. Therefore, it is not optional. Both optionalstr and 
        // optionalint have default values assigned to them. They are optional.
        public void ExampleMethod(int required, string optionalstr = "default string",
            int optionalint = 10)
        {
            Console.WriteLine("{0}: {1}, {2}, and {3}.", _name, required, optionalstr,
                optionalint);
        }
    }

    // The output from this example is the following:
    // Default name: 1, One, and 1.
    // Default name: 2, Two, and 10.
    // Default name: 3, default string, and 10.
    // Provided name: 1, One, and 1.
    // Provided name: 2, Two, and 10.
    // Provided name: 3, default string, and 10.
    // Default name: 3, default string, and 4.

}


COM 인터페이스

1. 명명된 인수 및 선택적 인수 외에 동적 개체에 대한 지원 및 기타 개선 기능을 사용하면 Office Automation API 같은 COM API와의 상호 운용성이 대폭 향상됩니다.

2. 예를 들어, Microsoft Office Excel Range 인터페이스의 Microsoft Office Excel AutoFormat 메서드에는 매개 변수가 7개 있고 이들 매개 변수는 모두 선택적입니다.

3. 다음 그림에서는 이러한 매개 변수를 보여 줍니다.



AutoFormat 매개 변수

4. C# 3.0 및 이전 버전에서 인수는 다음 예제와 같이 각 매개 변수에 대해 필요합니다.


// In C# 3.0 and earlier versions, you need to supply an argument for
// every parameter. The following call specifies a value for the first
// parameter, and sends a placeholder value for the other six. The
// default values are used for those parameters.
var excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.Workbooks.Add();
excelApp.Visible = true;

var myFormat = 
    Microsoft.Office.Interop.Excel.XlRangeAutoFormat.xlRangeAutoFormatAccounting1;

excelApp.get_Range("A1", "B4").AutoFormat(myFormat, Type.Missing, 
    Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);


5. 그러나 C# 4.0에 도입된 명명된 인수 및 선택적 인수를 사용하면 AutoFormat 호출을 대폭 단순화할 수 있습니다

6. 명명된 인수 및 선택적 인수를 사용할 경우 기본값을 변경하지 않을 선택적 매개 변수에 대해서는 인수를 생략할 수 있습니다.

7. 다음 호출에서는 매개 변수 7개 중 하나에 대해서만 값이 지정됩니다.


// The following code shows the same call to AutoFormat in C# 4.0. Only
// the argument for which you want to provide a specific value is listed.
excelApp.Range["A1", "B4"].AutoFormat( Format: myFormat );


8. 자세한 내용 및 예제를 보려면 방법: Office 프로그래밍에 명명된 인수와 선택적 인수 사용(C# 프로그래밍 가이드) 및 방법: Visual C# 기능을 사용하여 Office Interop 개체에 액세스(C# 프로그래밍 가이드)를 참조하십시오.


오버로드 확인

1. 명명된 인수 및 선택적 인수의 사용은 다음과 같이 오버로드 확인에 영향을 줍니다.

1) 메서드, 인덱서 또는 생성자는 각 매개 변수가 모두 선택적인 경우이거나 각 매개 변수가 이름 또는 위치를 기준으로 호출문의 단일 인수와 대응되고 이 인수를 해당 매개 변수의 형식으로 변환할 수 있는 경우에 실행 후보가 됩니다.

2) 둘 이상의 후보가 발견되면 기본 설정 변환에 대한 오버로드 확인 규칙은 명시적으로 지정된 인수에 적용됩니다. 선택적 매개 변수의 생략된 인수는 무시됩니다.

3) 두 후보 모두 동등한 것으로 평가될 경우에는 호출에서 인수가 생략된 선택적 매개 변수가 없는 후보를 실행합니다. 이는 오버로드 확인에서 매개 변수가 적은 후보를 우선시하는 일반적인 규칙이 적용되기 때문입니다.



방법: Office 프로그래밍에 명명된 인수와 선택적 인수 사용

1. Visual C# 2010에 도입된 명명된 인수 및 선택적 인수는 C# 프로그래밍의 편리성, 유연성 및 가독성을 향상시켜 줍니다.

2. 또한 이러한 기능은 Microsoft Office 자동화 API 같은 COM 인터페이스에 매우 쉽게 액세스할 수 있게 해 줍니다.

3. 다음 예제에서 ConvertToTable 메서드에는 열 및 행의 개수, 서식, 테두리, 글꼴, 색상 같은 표의 특징을 나타내는 매개 변수 16개가 있습니다.

4. 대부분의 경우 모든 매개 변수에 대해 특정 값을 지정할 필요는 없기 때문에 16개 매개 변수가 모두 선택적입니다.

5. 그러나 명명된 인수 및 선택적 인수가 없으면 각 매개 변수에 대해 값 또는 자리 표시자 값이 제공되어야 합니다.

6. 명명된 인수 및 선택적 인수를 사용하면 프로젝트에 필요한 매개 변수에 대해서만 값을 지정할 수 있습니다.

7. 다음 절차를 완료하려면 Microsoft Office Word가 컴퓨터에 설치되어 있어야 합니다.

참고

1. 일부 Visual Studio 사용자 인터페이스 요소의 경우 다음 지침에 설명된 것과 다른 이름 또는 위치가 시스템에 표시될 수 있습니다.

2. 이러한 요소는 사용하는 Visual Studio 버전 및 설정에 따라 결정됩니다. 자세한 내용은 Visual Studio IDE 개인 설정을 참조하십시오.


새 콘솔 응용 프로그램을 만들려면

1. Visual Studio를 시작합니다.

2. 파일 메뉴에서 새로 만들기를 가리킨 다음 프로젝트를 클릭합니다.

3. 템플릿 범주 창에서 Visual C#을 확장한 다음 Windows를 클릭합니다.

4. 템플릿 창 맨 위에 있는 대상 프레임워크 상자에 .NET Framework 4가 표시되어 있는지 확인합니다.

5. 템플릿 창에서 콘솔 응용 프로그램을 클릭합니다.

6. 이름 필드에 프로젝트 이름을 입력합니다.

7. 확인을 클릭합니다.

8. 솔루션 탐색기에 새 프로젝트가 나타납니다.


참조를 추가하려면

1. 솔루션 탐색기에서 프로젝트 이름을 마우스 오른쪽 단추로 클릭하고 참조 추가를 클릭합니다. 참조 추가 대화 상자가 나타납니다.

2. .NET 페이지의 구성 요소 이름 목록에서 Microsoft.Office.Interop.Word를 선택합니다.

3. 확인을 클릭합니다.


필요한 using 지시문을 추가하려면

1. 솔루션 탐색기에서 Program.cs 파일을 마우스 오른쪽 단추로 클릭하고 코드 보기를 클릭합니다.

2. 코드 파일의 맨 위에 다음 using 지시문을 추가합니다.


using Word = Microsoft.Office.Interop.Word;


Word 문서에 텍스트를 표시하려면

1. Program.cs의 Program 클래스에서 다음 메서드를 추가하여 Word 응용 프로그램 및 Word 문서를 만듭니다.

2. Add 메서드에는 선택적 매개 변수가 4개 있습니다.

3. 이 예제에서는 각 매개 변수의 기본값이 사용됩니다. 따라서, 메서드 호출문에서 인수가 전혀 필요하지 않습니다.


static void DisplayInWord()
{
    var wordApp = new Word.Application();
    wordApp.Visible = true;
    // docs is a collection of all the Document objects currently 
    // open in Word.
    Word.Documents docs = wordApp.Documents;

    // Add a document to the collection and name it doc. 
    Word.Document doc = docs.Add();
}


4. 메서드 끝부분에 다음 코드를 추가하여 문서에서 텍스트를 표시할 위치 및 표시할 텍스트를 정의합니다.


// Define a range, a contiguous area in the document, by specifying
// a starting and ending character position. Currently, the document
// is empty.
Word.Range range = doc.Range(0, 0);

// Use the InsertAfter method to insert a string at the end of the
// current range.
range.InsertAfter("Testing, testing, testing. . .");


응용 프로그램을 실행하려면

1. 다음 문을 Main에 추가합니다.


DisplayInWord();


2. Ctrl+F5를 눌러 프로젝트를 실행합니다. 지정된 텍스트를 포함하는 Word 문서가 나타납니다.




텍스트를 표로 변경하려면

1. ConvertToTable 메서드를 사용하여 표에서 텍스트를 둘러쌉니다.

2. 이 메서드에는 선택적 매개 변수가 16개 있습니다.

3. 다음 그림과 같이 IntelliSense에서는 선택적 매개 변수를 괄호로 둘러쌉니다.



ConvertToTable 매개 변수

4. 명명된 인수와 선택적 인수를 사용하면 변경할 매개 변수에 대해서만 값을 지정할 수 있습니다.

5. DisplayInWord 메서드의 끝부분에 다음 코드를 추가하여 간단한 표를 만듭니다.

6. 이 인수는 range의 텍스트 문자열에 있는 쉼표로 표의 셀이 구분되도록 지정합니다.


// Convert to a simple table. The table will have a single row with
// three columns.
range.ConvertToTable(Separator: ",");


7. 이전 버전의 C#에서 ConvertToTable을 호출하려면 다음 코드와 같이 각 매개 변수에 대한 참조 인수가 필요합니다.


// Call to ConvertToTable in Visual C# 2008 or earlier. This code
// is not part of the solution.
var missing = Type.Missing;
object separator = ",";
range.ConvertToTable(ref separator, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing,
    ref missing);


8. Ctrl+F5를 눌러 프로젝트를 실행합니다.


다른 매개 변수를 사용하여 테스트하려면

1. 표에 열 1개와 행 3개가 있도록 변경하려면 DisplayInWord에서 마지막 줄을 다음 문으로 바꾼 다음 Ctrl+F5를 누릅니다.


range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1);


2. 미리 정의된 서식을 표에 지정하려면 DisplayInWord에서 마지막 줄을 다음 문으로 바꾼 다음 Ctrl+F5를 누릅니다.

3. 서식은 WdTableFormat 상수 중 하나일 수 있습니다.


range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1,
    Format: Word.WdTableFormat.wdTableFormatElegant);


예제

1. 다음 코드에는 전체 예제가 포함되어 있습니다.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Word = Microsoft.Office.Interop.Word;


namespace OfficeHowTo
{
    class WordProgram
    {
        static void Main(string[] args)
        {
            DisplayInWord();
        }

        static void DisplayInWord()
        {
            var wordApp = new Word.Application();
            wordApp.Visible = true;
            // docs is a collection of all the Document objects currently 
            // open in Word.
            Word.Documents docs = wordApp.Documents;

            // Add a document to the collection and name it doc. 
            Word.Document doc = docs.Add();

            // Define a range, a contiguous area in the document, by specifying
            // a starting and ending character position. Currently, the document
            // is empty.
            Word.Range range = doc.Range(0, 0);

            // Use the InsertAfter method to insert a string at the end of the
            // current range.
            range.InsertAfter("Testing, testing, testing. . .");

            // You can comment out any or all of the following statements to
            // see the effect of each one in the Word document.

            // Next, use the ConvertToTable method to put the text into a table. 
            // The method has 16 optional parameters. You only have to specify
            // values for those you want to change.

            // Convert to a simple table. The table will have a single row with
            // three columns.
            range.ConvertToTable(Separator: ",");

            // Change to a single column with three rows..
            range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1);

            // Format the table.
            range.ConvertToTable(Separator: ",", AutoFit: true, NumColumns: 1,
                Format: Word.WdTableFormat.wdTableFormatElegant);
        }
    }
}




생성자


1. 클래스나 구조체를 만들 때마다 해당 생성자가 호출됩니다.

2. 클래스나 구조체에는 서로 다른 인수를 사용하는 여러 생성자가 있을 수 있습니다.

3. 프로그래머는 생성자를 통해 기본값을 설정하고, 인스턴스화를 제한하며, 융통성 있고 읽기 쉬운 코드를 작성할 수 있습니다.

4. 자세한 내용 및 예제를 보려면 생성자 사용(C# 프로그래밍 가이드) 및 인스턴스 생성자(C# 프로그래밍 가이드)를 참조하십시오.

5. 사용자가 작성한 개체에 대해 생성자를 제공하지 않으면 C#에서 기본적으로 개체를 인스턴스화하고 모든 멤버 변수에 기본값을 설정하는 생성자를 만듭니다.

6. 기본값은 기본값 표(C# 참조)에 나열되어 있습니다.

7. 자세한 내용과 예제를 보려면 인스턴스 생성자(C# 프로그래밍 가이드)을 참조하십시오.

8. 정적 클래스와 구조체에도 생성자가 있을 수 있습니다.

9. 자세한 내용과 예제를 보려면 정적 생성자(C# 프로그래밍 가이드)을 참조하십시오.



생성자 사용


1. 경우는 클래스 또는 구조체 는 만들어지면 해당 생성자가 호출됩니다.

2. 생성자는 클래스 또는 구조체와 같은 이름을 하고 일반적으로 새 개체의 데이터 멤버를 초기화 합니다.

3. 다음 예제에서는 간단한 생성자를 사용하여 Taxi라는 클래스를 정의합니다.

4. 그런 다음 new 연산자를 사용하여 이 클래스를 인스턴스화합니다.

5. 새 개체에 대한 메모리를 할당한 직후에 new 연산자에 의해 Taxi 생성자가 호출됩니다.


public class Taxi
{
    public bool isInitialized;
    public Taxi()
    {
        isInitialized = true;
    }
}

class TestTaxi
{
    static void Main()
    {
        Taxi t = new Taxi();
        Console.WriteLine(t.isInitialized);
    }
}


6. 매개 변수를 사용하지 않는 생성자를 기본 생성자라고 합니다

7. 기본 생성자는 new 연산자에 아무런 인수도 전달하지 않은 채 new 연산자를 사용하여 개체를 인스턴스화할 때마다 호출됩니다.

8. 자세한 내용은 인스턴스 생성자(C# 프로그래밍 가이드)을 참조하십시오.

9. 클래스가 정적 클래스가 아닌 경우 생성자가 없는 클래스에는 클래스 인스턴스화에 필요한 공용 기본 생성자가 C# 컴파일러에 의해 제공됩니다.

10. 자세한 내용은 정적 클래스 및 정적 클래스 멤버(C# 프로그래밍 가이드)을 참조하십시오.

11. 다음과 같이 생성자를 private 생성자로 만들면 클래스가 인스턴스화되지 않도록 방지할 수 있습니다.


class NLog
{
    // Private Constructor:
    private NLog() { }

    public static double e = Math.E;  //2.71828...
}


12. 자세한 내용은 전용 생성자(C# 프로그래밍 가이드)을 참조하십시오.

13. 구조체 형식에 대한 생성자는 클래스 생성자와 비슷하지만 structs에는 컴파일러를 통해 생성자가 자동으로 제공되므로 명시적 기본 생성자가 포함될 수 없습니다.

14. 이 생성자는 struct의 각 필드를 기본값으로 초기화합니다.

15. 자세한 내용은 기본값 표(C# 참조)을 참조하십시오.

16. 그러나 이 기본 생성자는 new를 사용하여 struct를 인스턴스화한 경우에만 호출됩니다.

17. 예를 들어 다음 코드에서는 Int32의 기본 생성자를 사용하므로 정수가 초기화됩니다.


int i = new int();
Console.WriteLine(i);


18. 그러나 다음 코드에서는 new를 사용하지 않으며 초기화되지 않은 개체를 사용하려고 하기 때문에 컴파일러 오류가 발생합니다.


int i;
Console.WriteLine(i);


19. 또는 다음 예제와 같이 structs를 기반으로 하는 개체(모든 기본 제공 숫자 형식 포함)를 초기화하거나 할당한 후에 사용할 수 있습니다.


int a = 44;  // Initialize the value type...
int b;
b = 33;      // Or assign it before using it.
Console.WriteLine("{0}, {1}", a, b);


20. 따라서 값 형식에 대한 기본 생성자를 호출할 필요가 없습니다.

21. 클래스와 structs 모두 매개 변수를 사용하는 생성자를 정의할 수 있습니다.

22. 매개 변수를 사용하는 생성자는 new 문이나 base 문을 통해 호출해야 합니다.

23. 클래스와 structs는 여러 생성자를 정의할 수도 있으며, 둘 모두 기본 생성자를 정의할 필요가 없습니다.

24. 예를 들면 다음과 같습니다.


public class Employee
{
    public int salary;

    public Employee(int annualSalary)
    {
        salary = annualSalary;
    }

    public Employee(int weeklySalary, int numberOfWeeks)
    {
        salary = weeklySalary * numberOfWeeks;
    }
}


25. 다음 문 중 하나를 사용하여 이 클래스를 만들 수 있습니다.


Employee e1 = new Employee(30000);
Employee e2 = new Employee(500, 52);


26. 생성자에서는 base 키워드를 사용하여 기본 클래스의 생성자를 호출할 수 있습니다.

27. 예를 들면 다음과 같습니다.


public class Manager : Employee
{
    public Manager(int annualSalary)
        : base(annualSalary)
    {
        //Add further instructions here.
    }
}


28. 이 예제에서 기본 클래스의 생성자는 생성자의 블록이 실행되기 전에 호출됩니다.

29. base 키워드는 매개 변수와 함께 사용하거나 매개 변수 없이 사용할 수 있습니다.

30. 생성자에 대한 모든 매개 변수는 base에 대한 매개 변수로 사용하거나 식의 일부로 사용할 수 있습니다.

31. 자세한 내용은 base(C# 참조)을 참조하십시오.

32. 파생 클래스에서 base 키워드를 사용하여 기본 클래스 생성자를 명시적으로 호출하지 않은 경우 기본 생성자가 있으면 이 생성자가 암시적으로 호출됩니다.

33. 즉, 다음과 같은 생성자 선언도 동일한 결과를 낳습니다.


public Manager(int initialdata)
{
    //Add further instructions here.
}


34. 기본 클래스에서 기본 생성자를 제공하지 않으면 파생 클래스에서 base를 사용하여 기본 생성자를 명시적으로 호출해야 합니다.

35. 생성자에서 this 키워드를 사용하여 동일한 개체의 다른 생성자를 호출할 수 있습니다.

36. base와 마찬가지로 this는 매개 변수와 함께 사용하거나 매개 변수 없이 사용할 수 있으며, 생성자의 임의의 매개 변수를 this에 대한 매개 변수로 사용하거나 식의 일부로 사용할 수 있습니다.

37. 예를 들어, 위 예제의 두 번째 생성자는 this를 사용하여 다시 작성할 수 있습니다.


public Employee(int weeklySalary, int numberOfWeeks)
    : this(weeklySalary * numberOfWeeks)
{
}


38. 위의 예제에서 this 키워드를 사용하면 이 생성자가 호출됩니다.


public Employee(int annualSalary)
{
    salary = annualSalary;
}


39. 생성자는 public, private, protected, internal 또는 protectedinternal로 표시할 수 있습니다.

40. 이러한 액세스 한정자는 클래스 사용자가 클래스를 생성하는 방식을 정의합니다.

41. 자세한 내용은 액세스 한정자를 참조하십시오.

42. static 키워드를 사용하여 생성자를 정적으로 선언할 수 있습니다.

43. 정적 생성자는 정적 필드에 액세스하기 직전에 자동으로 호출되며 일반적으로 정적 클래스 멤버를 초기화하는 데 사용됩니다.

44. 자세한 내용은 정적 생성자를 참조하십시오.





인스턴스 생성자


1. new 식을 사용하여 클래스의 개체를 만드는 경우 인스턴스 생성자를 사용하여 인스턴스 멤버 변수를 만들고 초기화합니다.

2. 정적 클래스나 비정적 클래스의 정적 변수를 초기화하려면 정적 생성자를 정의해야 합니다.

3. 자세한 내용은 정적 생성자(C# 프로그래밍 가이드)를 참조하십시오.

4. 다음 예제에서는 인스턴스 생성자를 보여 줍니다.


class CoOrds
{
    public int x, y;

    // constructor
    public CoOrds()
    {
        x = 0;
        y = 0;
    }
}


참고

명확한 이해를 돕기 위해 이 클래스에는 public 필드가 포함되어 있습니다. public 필드를 사용하면 프로그램의 모든 메서드가 위치에 관계없이 개체의 내부 작업에 확인을 거치지 않은 채 무제한으로 액세스할 수 있으므로 이 방법은 실제로 사용하지 않는 것이 좋습니다. 데이터 멤버는 일반적으로 전용이어야 하며 클래스 메서드 및 속성을 통해서만 액세스할 수 있어야 합니다.


5. 이 인스턴스 생성자는 CoOrds 클래스를 기반으로 개체가 만들어질 때마다 호출됩니다.

6. 이와 같이 인수를 사용하지 않는 생성자를 기본 생성자라고 합니다.

7. 그러나 일반적으로는 다른 생성자를 추가로 제공하는 것이 유용합니다.

8. 예를 들어, 데이터 멤버에 대한 초기 값을 지정할 수 있도록 CoOrds 클래스에 생성자를 추가할 수 있습니다.


// A constructor with two arguments:
public CoOrds(int x, int y)
{
    this.x = x;
    this.y = y;
}


9. 이렇게 하면 다음과 같이 특정 초기 값이나 기본값으로 CoOrd 개체를 만들 수 있습니다.


CoOrds p1 = new CoOrds();
CoOrds p2 = new CoOrds(5, 3);


10. 클래스에 생성자가 없는 경우 기본 생성자가 자동으로 생성되며, 기본값을 사용하여 개체 필드가 초기화됩니다.

11. 예를 들어, int는 0으로 초기화됩니다.

12. 기본값에 대한 자세한 내용은 기본값 표(C# 참조)를 참조하십시오.

13. 따라서, CoOrds 클래스 기본 생성자가 모든 데이터 멤버를 0으로 초기화하므로 클래스의 작동 방식을 변경하지 않고도 이를 완전히 제거할 수 있습니다.

14. 여러 생성자를 사용하는 전체 예제는 이 항목의 뒷부분에 있는 예제 1을 참조하고, 자동으로 생성된 생성자의 예제는 예제 2를 참조하십시오.

15. 인스턴스 생성자를 사용하여 기본 클래스의 인스턴스 생성자를 호출할 수도 있습니다.

16. 클래스 생성자는 다음과 같이 이니셜라이저를 통해 기본 클래스의 생성자를 호출할 수 있습니다.


class Circle : Shape
{
    public Circle(double radius)
        : base(radius, 0)
    {
    }
}


17. 이 예제에서 Circle 클래스는 Circle을 파생하는 데 사용되는 Shape에서 제공하는 생성자에 반지름과 높이를 나타내는 값을 전달합니다.

18. Shape 및 Circle을 사용하는 전체 예제는 이 항목의 예제 3을 참조하십시오.


예제 1

1. 다음 예제에서는 인수 없는 클래스 생성자와 두 개의 인수가 있는 클래스 생성자라는 2개의 클래스 생성자를 가진 클래스에 대해 설명합니다.


class CoOrds
{
    public int x, y;

    // Default constructor:
    public CoOrds()
    {
        x = 0;
        y = 0;
    }

    // A constructor with two arguments:
    public CoOrds(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    // Override the ToString method:
    public override string ToString()
    {
        return (String.Format("({0},{1})", x, y));
    }
}

class MainClass
{
    static void Main()
    {
        CoOrds p1 = new CoOrds();
        CoOrds p2 = new CoOrds(5, 3);

        // Display the results using the overriden ToString method:
        Console.WriteLine("CoOrds #1 at {0}", p1);
        Console.WriteLine("CoOrds #2 at {0}", p2);
        Console.ReadKey();
    }
}
/* Output:
 CoOrds #1 at (0,0)
 CoOrds #2 at (5,3)        
*/


예제 2

1. 이 예제에서 Person 클래스는 생성자를 갖지 않는데, 이 경우 기본 생성자가 자동으로 생성되며 필드는 기본값으로 초기화됩니다.


public class Person
{
    public int age;
    public string name;
}

class TestPerson
{
    static void Main()
    {
        Person person = new Person();

        Console.WriteLine("Name: {0}, Age: {1}", person.name, person.age);
        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output:  Name: , Age: 0


2. age의 기본값은 0이고 name의 기본값은 null입니다.

3. 기본값에 대한 자세한 내용은 기본값 표(C# 참조)를 참조하십시오.


예제 3

1. 다음 예제에서는 기본 클래스 이니셜라이저를 사용하여 설명합니다.

2. Circle 클래스는 일반 클래스 Shape에서 파생되며 Cylinder 클래스는 Circle 클래스에서 파생됩니다.

3. 파생된 각 클래스의 생성자는 자체의 기본 클래스 이니셜라이저를 사용합니다.


abstract class Shape
{
    public const double pi = Math.PI;
    protected double x, y;

    public Shape(double x, double y)
    {
        this.x = x;
        this.y = y;
    }

    public abstract double Area();
}

class Circle : Shape
{
    public Circle(double radius)
        : base(radius, 0)
    {
    }
    public override double Area()
    {
        return pi * x * x;
    }
}

class Cylinder : Circle
{
    public Cylinder(double radius, double height)
        : base(radius)
    {
        y = height;
    }

    public override double Area()
    {
        return (2 * base.Area()) + (2 * pi * x * y);
    }
}

class TestShapes
{
    static void Main()
    {
        double radius = 2.5;
        double height = 3.0;

        Circle ring = new Circle(radius);
        Cylinder tube = new Cylinder(radius, height);

        Console.WriteLine("Area of the circle = {0:F2}", ring.Area());
        Console.WriteLine("Area of the cylinder = {0:F2}", tube.Area());

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Area of the circle = 19.63
    Area of the cylinder = 86.39
*/




전용 생성자


1. 전용 생성자는 특수 인스턴스 생성자입니다.

2. 이것은 정적 멤버만 포함하는 클래스에서 일반적으로 사용됩니다.

3. 클래스에 전용 생성자만 한 개 이상 있고 공용 생성자는 없을 경우 중첩 클래스를 제외한 다른 클래스는 이 클래스의 인스턴스를 만들 수 없습니다.

4. 예를 들면 다음과 같습니다.


class NLog
{
    // Private Constructor:
    private NLog() { }

    public static double e = Math.E;  //2.71828...
}


5. 빈 생성자를 선언하면 기본 생성자가 자동으로 생성되지 않습니다.

6. 액세스 한정자를 생성자와 함께 사용하지 않을 경우에는 기본적으로 전용 생성자가 됩니다.

7. 그러나 클래스가 인스턴스화될 수 없음을 분명히 하려면 대개 private 한정자를 명시적으로 사용합니다.

8. 전용 생성자는 Math 클래스처럼 인스턴스 필드나 메서드가 없는 경우 또는 클래스의 인스턴스를 가져오기 위해 메서드가 호출되는 경우에 클래스의 인스턴스가 생성되지 않도록 방지하는 데 사용됩니다.

9. 클래스의 메서드가 모두 정적 메서드이면 전체 클래스를 정적 클래스로 만드는 것이 좋습니다.

10.  자세한 내용은 정적 클래스 및 정적 클래스 멤버(C# 프로그래밍 가이드)를 참조하십시오.


예제

1. 다음은 전용 생성자를 사용하는 클래스의 예제입니다.


public class Counter
{
    private Counter() { }
    public static int currentCount;
    public static int IncrementCount()
    {
        return ++currentCount;
    }
}

class TestCounter
{
    static void Main()
    {
        // If you uncomment the following statement, it will generate
        // an error because the constructor is inaccessible:
        // Counter aCounter = new Counter();   // Error

        Counter.currentCount = 100;
        Counter.IncrementCount();
        Console.WriteLine("New count: {0}", Counter.currentCount);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: New count: 101


2. 예제에서 다음 문을 주석으로 처리하지 않으면 보호 수준 때문에 이 생성자에 액세스할 수 없어서 오류가 발생합니다.


// Counter aCounter = new Counter();   // Error




정적 생성자


1. 정적 생성자는 정적 데이터를 초기화하거나 한 번만 수행하면 되는 특정 작업을 수행하는 데 사용됩니다.

2. 이 생성자는 첫 번째 인스턴스가 만들어지기 전이나 정적 멤버가 참조되기 전에 자동으로 호출됩니다.


class SimpleClass
{
    // Static variable that must be initialized at run time.
    static readonly long baseline;

    // Static constructor is called at most one time, before any
    // instance constructor is invoked or member is accessed.
    static SimpleClass()
    {
        baseline = DateTime.Now.Ticks;
    }
}


3. 정적 생성자에는 다음과 같은 속성이 있습니다.

1) 정적 생성자는 액세스 한정자를 사용하지 않고 매개 변수를 갖지 않습니다.

2) 정적 생성자는 첫 번째 인스턴스가 만들어지기 전이나 정적 멤버가 참조되기 전에 클래스를 초기화하기 위해 자동으로 호출됩니다.

3) 정적 생성자는 직접 호출할 수 없습니다.

4) 사용자는 프로그램에서 정적 생성자가 실행되는 시기를 제어할 수 없습니다.

5) 정적 생성자는 일반적으로 클래스에서 로그 파일을 사용할 때 이 파일에 항목을 쓰기 위해 사용됩니다.

6) 정적 생성자는 생성자에서 LoadLibrary 메서드를 호출할 수 있는 경우 비관리 코드에 대한 래퍼 클래스를 만들 때도 유용합니다.

7) 정적 생성자에서 예외를 throw하면 런타임은 다시 이 생성자를 호출하지 않으므로 해당 형식은 프로그램이 실행되는 응용 프로그램 도메인의 수명 동안 초기화되지 않은 상태로 남아 있게 됩니다.


예제

1. 이 예제에서 Bus 클래스에는 정적 생성자가 있습니다. Bus의 첫 번째 인스턴스를 만들면(bus1) 정적 생성자가 호출되어 클래스를 초기화합니다.

2. 샘플 출력에서는 Bus의 두 인스턴스를 만들어도 정적 생성자가 한 번만 실행되며, 인스턴스 생성자가 실행되기 전에 정적 생성자가 실행되는지 확인합니다.


 public class Bus
 {
     // Static variable used by all Bus instances.
     // Represents the time the first bus of the day starts its route.
     protected static readonly DateTime globalStartTime;

     // Property for the number of each bus.
     protected int RouteNumber { get; set; }

     // Static constructor to initialize the static variable.
     // It is invoked before the first instance constructor is run.
     static Bus()
     {
         globalStartTime = DateTime.Now;

         // The following statement produces the first line of output, 
         // and the line occurs only once.
         Console.WriteLine("Static constructor sets global start time to {0}",
             globalStartTime.ToLongTimeString());
     }

     // Instance constructor.
     public Bus(int routeNum)
     {
         RouteNumber = routeNum;
         Console.WriteLine("Bus #{0} is created.", RouteNumber);
     }

     // Instance method.
     public void Drive()
     {
         TimeSpan elapsedTime = DateTime.Now - globalStartTime;

         // For demonstration purposes we treat milliseconds as minutes to simulate
         // actual bus times. Do not do this in your actual bus schedule program!
         Console.WriteLine("{0} is starting its route {1:N2} minutes after global start time {2}.",
                                 this.RouteNumber,
                                 elapsedTime.TotalMilliseconds,
                                 globalStartTime.ToShortTimeString());
     }
 }

 class TestBus
 {
     static void Main()
     {
         // The creation of this instance activates the static constructor.
         Bus bus1 = new Bus(71);

         // Create a second bus.
         Bus bus2 = new Bus(72);

         // Send bus1 on its way.
         bus1.Drive();

         // Wait for bus2 to warm up.
         System.Threading.Thread.Sleep(25);

         // Send bus2 on its way.
         bus2.Drive();

         // Keep the console window open in debug mode.
         System.Console.WriteLine("Press any key to exit.");
         System.Console.ReadKey();
     }
 }
 /* Sample output:
     Static constructor sets global start time to 3:57:08 PM.
     Bus #71 is created.
     Bus #72 is created.
     71 is starting its route 6.00 minutes after global start time 3:57 PM.
     72 is starting its route 31.00 minutes after global start time 3:57 PM.      
*/




방법: 복사 생성자 작성


1. C#는 개체에 대한 복사 생성자를 제공하지 않지만 사용자가 직접 작성할 수 있습니다.


예제

1. 다음 예에서 Person클래스는 해당 인수로 Person 인스턴스를 받아들이는 복사 생성자를 정의합니다.

2. 인수의 속성 값은 Person의 새 인스턴스 속성에 할당됩니다.

3. 코드는 클래스의 인스턴스 생성자에게 복사하려는 Name 및 Age 인스턴스 속성을 보내는 다른 복사 생성자를 포함합니다.


class Person
{
    // Copy constructor.
    public Person(Person previousPerson)
    {
        Name = previousPerson.Name;
        Age = previousPerson.Age;
    }

    //// Alternate copy constructor calls the instance constructor.
    //public Person(Person previousPerson)
    //    : this(previousPerson.Name, previousPerson.Age)
    //{
    //}

    // Instance constructor.
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public int Age { get; set; }

    public string Name { get; set; }

    public string Details()
    {
        return Name + " is " + Age.ToString();
    }
}

class TestPerson
{
    static void Main()
    {
        // Create a Person object by using the instance constructor.
        Person person1 = new Person("George", 40);

        // Create another Person object, copying person1.
        Person person2 = new Person(person1);

        // Change each person's age. 
        person1.Age = 39;
        person2.Age = 41;

        // Change person2's name.
        person2.Name = "Charles";

        // Show details to verify that the name and age fields are distinct.
        Console.WriteLine(person1.Details());
        Console.WriteLine(person2.Details());

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: 
// George is 39
// Charles is 41




소멸자


1. 소멸자는 클래스의 인스턴스를 소멸하는 데 사용됩니다.


설명

1) 소멸자는 구조체에 정의할 수 없습니다. 이것은 클래스에서만 사용할 수 있습니다.

2) 클래스는 하나의 소멸자만 가질 수 있습니다.

3) 소멸자는 상속되거나 오버로드될 수 없습니다.

4) 소멸자는 사용자가 호출할 수 없으며, 자동으로 호출됩니다.

5) 소멸자는 한정자를 사용하거나 매개 변수를 갖지 않습니다.


1. 예를 들어, Car 클래스에 대한 소멸자 선언은 다음과 같습니다.


class Car
{
    ~Car()  // destructor
    {
        // cleanup statements...
    }
}


2. 소멸자는 개체의 기본 클래스에서 암시적으로 Finalize를 호출합니다.

3. 따라서 위의 소멸자 코드는 암시적으로 다음과 같은 코드로 변환됩니다.


protected override void Finalize()
{
    try
    {
        // Cleanup statements...
    }
    finally
    {
        base.Finalize();
    }
}


4. 즉, Finalize 메서드는 최대 파생 인스턴스부터 최소 파생 인스턴스까지 상속 체인에 있는 모든 인스턴스에 대해 재귀적으로 호출됩니다.


참고

1. 빈 소멸자는 사용하지 않는 것이 좋습니다.

2. 클래스에 소멸자가 포함된 경우 Finalize 큐에 엔트리가 만들어집니다.

3. 소멸자가 호출되면 큐 처리를 위해 가비지 수집기가 호출됩니다.

4. 그러므로 빈 소멸자는 성능 저하를 가져올 뿐입니다.


5. 소멸자가 호출되는 시기는 가비지 수집기에서 결정하므로 프로그래머는 이 시기를 제어할 수 없습니다.

6. 가비지 수집기는 응용 프로그램에서 더 이상 사용하지 않는 개체를 확인합니다.

7. 개체를 소멸할 수 있는 것으로 판단된 경우 소멸자가 있으면 이를 호출하고 개체를 저장하는 데 사용된 메모리를 회수합니다.

8. 소멸자는 프로그램을 종료할 때도 호출됩니다.

10. Collect를 호출하여 가비지 수집을 강제로 실행할 수 있지만, 이렇게 하면 성능 문제가 발생할 수 있으므로 피하는 것이 좋습니다.


소멸자를 사용하여 리소스 해제

1. 일반적으로 C#에는 가비지 수집 기능이 있는 런타임을 대상으로 하지 않는 언어로 개발할 때 필요한 것과 같은 메모리 관리가 필요하지 않습니다.

2. 왜냐하면, .NET Framework 가비지 수집기에서 개체에 대한 메모리 할당과 해제를 암시적으로 관리하기 때문입니다.

3. 그러나 창, 파일 및 네트워크 연결처럼 관리되지 않는 리소스를 응용 프로그램에서 캡슐화할 경우, 소멸자를 사용하여 이 리소스를 해제해야 합니다.

4. 개체가 소멸 대상이 되면 가비지 수집기는 해당 개체의 Finalize 메서드를 실행합니다.


리소스의 명시적 해제

1. 응용 프로그램에서 부담이 큰 외부 리소스를 사용하는 경우에는 가비지 수집기에서 개체를 정리하기 전에 리소스를 명시적으로 해제하는 것도 좋은 방법입니다.

2. 이렇게 하려면 개체에 필요한 정리를 수행하는 IDisposable 인터페이스의 Dispose 메서드를 구현합니다.

3. 이로 인해 응용 프로그램 성능이 대폭 향상될 수 있습니다.

4. 이러한 명시적 리소스 제어와 함께, 소멸자는 Dispose 메서드 호출이 실패할 경우 리소스를 정리하는 보호 수단이 됩니다.

5. 리소스 정리에 대한 자세한 내용은 다음 항목을 참조하십시오.

1) Cleaning Up Unmanaged Resources

2) Implementing a Dispose Method

3) using 문(C# 참조)


예제

1. 다음 예제에서는 상속 체인을 만드는 세 개의 클래스를 생성합니다.

2. First 클래스가 기본 클래스이고 Second 클래스는 First에서 파생되며 Third는 Second에서 파생됩니다.

3. 세 클래스에는 모두 소멸자가 있습니다.

4. Main()에서 최대 파생 클래스의 인스턴스가 만들어집니다.

5. 프로그램을 실행하면 세 클래스의 소멸자가 최대 파생 클래스부터 최소 파생 클래스까지 순서대로 자동 호출됩니다.


class First
{
    ~First()
    {
        System.Diagnostics.Trace.WriteLine("First's destructor is called.");
    }
}

class Second : First
{
    ~Second()
    {
        System.Diagnostics.Trace.WriteLine("Second's destructor is called.");
    }
}

class Third : Second
{
    ~Third()
    {
        System.Diagnostics.Trace.WriteLine("Third's destructor is called.");
    }
}

class TestDestructors
{
    static void Main()
    {
        Third t = new Third();
    }

}
/* Output (to VS Output Window):
    Third's destructor is called.
    Second's destructor is called.
    First's destructor is called.
*/




개체 및 컬렉션 이니셜라이저


1. 개체 이니셜라이저를 사용하면 명시적으로 생성자를 호출한 다음 할당문 줄을 추가하지 않고도 생성 시 개체의 모든 액세스 가능한 필드나 속성에 값을 할당할 수 있습니다.

2. 개체 이니셜라이저 구문을 사용하면 생성자의 인수를 지정하거나 인수(및 괄호 구문)를 생략할 수 있습니다.

3. 다음 예제는 명명된 형식인 Cat의 개체 이니셜라이저를 사용하는 방법과 기본 생성자를 호출하는 방법을 보여 줍니다.

4. Cat 클래스의 자동 구현된 속성 사용을 확인합니다.

5. 자세한 내용은 자동으로 구현된 속성(C# 프로그래밍 가이드)을 참조하세요.


class Cat
{
    // Auto-implemented properties.
    public int Age { get; set; }
    public string Name { get; set; }
}


Cat cat = new Cat { Age = 10, Name = "Fluffy" };


익명 형식의 개체 이니셜라이저

1. 개체 이니셜라이저는 모든 컨텍스트에서 사용할 수 있지만 특히 LINQ 쿼리 식에 유용합니다.

2. 쿼리 식은 익명 형식을 자주 사용합니다.

3. 이 형식은 다음 선언에 표시된 바와 같이 개체 이니셜라이저를 사용하는 경우에만 초기화될 수 있습니다.


var pet = new { Age = 10, Name = "Fluffy" };


4. 익명 형식을 사용하면 select 쿼리 식의 LINQ 절에서 원래 시퀀스의 개체를 값과 모양이 원본과 다를 수 있는 개체로 변환할 수 있습니다.

5. 이 기능은 각 개체의 일부 정보만 시퀀스에 저장하려는 경우에 유용합니다.

6. 다음 예제에서 제품 개체(p)는 많은 필드와 메서드를 포함하며, 제품 이름과 단위 가격이 들어 있는 개체 시퀀스만 만들려 한다고 가정합니다.


var productInfos =
    from p in products
    select new { p.ProductName, p.UnitPrice };


7. 이 쿼리를 실행하면 다음 예제와 같이 productInfos 문에서 액세스할 수 있는 개체 시퀀스가 foreach 변수에 포함됩니다.


foreach(var p in productInfos){...}


8. 새 익명 형식의 각 개체에는 원래 개체의 속성이나 필드와 동일한 이름을 받는 두 개의 public 속성이 있습니다.

9. 익명 형식을 만들 때 필드 이름을 바꿀 수도 있습니다.

10. 다음 예제에서는 UnitPrice 필드의 이름을 Price로 바꿉니다.


select new {p.ProductName, Price = p.UnitPrice};


nullable 형식의 개체 이니셜라이저

1. 개체 이니셜라이저에 nullable 구조체를 사용하는 것은 컴파일 타임 오류입니다.


컬렉션 이니셜라이저

1. 컬렉션 이니셜라이저를 사용하면 IEnumerable을 구현하는 컬렉션 클래스나 Add 확장 메서드가 있는 클래스를 초기화할 때 하나 이상의 요소 이니셜라이저를 지정할 수 있습니다.

2. 요소 이니셜라이저는 단순한 값, 식 또는 개체 이니셜라이저일 수 있습니다.

3. 컬렉션 이니셜라이저를 사용하면 소스 코드에서 클래스의 Add 메서드에 대한 호출을 여러 번 지정할 필요가 없습니다.

4. 컴파일러가 호출을 추가합니다.

5. 다음 예제에서는 두 개의 단순한 컬렉션 이니셜라이저를 보여 줍니다.


List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };


6. 다음 컬렉션 이니셜라이저는 개체 이니셜라이저를 사용하여 앞의 예제에서 정의된 Cat 클래스의 개체를 초기화합니다.

7. 개별 개체 이니셜라이저는 괄호로 묶이고 쉼표로 구분됩니다.


List<Cat> cats = new List<Cat>
{
    new Cat(){ Name = "Sylvester", Age=8 },
    new Cat(){ Name = "Whiskers", Age=2 },
    new Cat(){ Name = "Sasha", Age=14 }
};


8. 컬렉션의 null 메서드에서 허용하는 경우 Add을 컬렉션 이니셜라이저의 요소로 지정할 수 있습니다.


List<Cat> moreCats = new List<Cat>
{
    new Cat(){ Name = "Furrytail", Age=5 },
    new Cat(){ Name = "Peaches", Age=4 },
    null
};


9. 컬렉션이 인덱싱을 지원하는 경우 인덱싱된 요소를 지정할 수 있습니다.


var numbers = new Dictionary<int, string> { 
    [7] = "seven", 
    [9] = "nine", 
    [13] = "thirteen" 
};


예제


// The following code consolidates examples from the topic.

class ObjInitializers
{
    class Cat
    {
        // Auto-implemented properties.
        public int Age { get; set; }
        public string Name { get; set; }
    }

    static void Main()
    {
        Cat cat = new Cat { Age = 10, Name = "Fluffy" };

        List<Cat> cats = new List<Cat>
        {
            new Cat(){ Name = "Sylvester", Age=8 },
            new Cat(){ Name = "Whiskers", Age=2 },
            new Cat(){ Name = "Sasha", Age=14 }
        };

        List<Cat> moreCats = new List<Cat>
        {
            new Cat(){ Name = "Furrytail", Age=5 },
            new Cat(){ Name = "Peaches", Age=4 },
            null
        };

        // Display results.
        System.Console.WriteLine(cat.Name);

        foreach (Cat c in cats)
            System.Console.WriteLine(c.Name);

        foreach (Cat c in moreCats)
            if (c != null)
                System.Console.WriteLine(c.Name);
            else
                System.Console.WriteLine("List element has null value.");
    }
    // Output:
    //Fluffy
    //Sylvester
    //Whiskers
    //Sasha
    //Furrytail
    //Peaches
    //List element has null value.
}




방법: 개체 이니셜라이저를 사용하여 개체 초기화


1. 개체 이니셜라이저를 사용하면 형식의 생성자를 명시적으로 호출하지 않고도 선언적 방식으로 형식 개체를 초기화할 수 있습니다.

2. 다음의 예제에서는 명명된 개체와 함께 개체 이니셜라이저를 사용하는 방법을 보여 줍니다.

3. 컴파일러는 먼저 기본 인스턴스 생성자에 액세스하고 멤버 초기화를 처리해서 개체 이니셜라이저를 처리합니다.

4. 따라서 기본 생성자가 클래스에서 private로 선언된 경우 공용 액세스가 필요한 개체 이니셜라이저가 실패합니다.

5. 익명 형식을 정의 하는 경우에 개체 이니셜라이저를 사용 해야 합니다.

6. 자세한 내용은 방법: 쿼리에서 요소 속성의 하위 집합 반환(C# 프로그래밍 가이드)을 참조하십시오.


예제

1. 다음 예제에서는 개체 이니셜라이저를 사용하여 새 StudentName 형식을 초기화하는 방법을 보여 줍니다.


public class Program
{
    public static void Main()
    {

        // Declare a StudentName by using the constructor that has two parameters.
        StudentName student1 = new StudentName("Craig", "Playstead");

        // Make the same declaration by using an object initializer and sending 
        // arguments for the first and last names. The default constructor is 
        // invoked in processing this declaration, not the constructor that has
        // two parameters.
        StudentName student2 = new StudentName
        {
            FirstName = "Craig",
            LastName = "Playstead",
        };

        // Declare a StudentName by using an object initializer and sending 
        // an argument for only the ID property. No corresponding constructor is
        // necessary. Only the default constructor is used to process object 
        // initializers.
        StudentName student3 = new StudentName
        {
            ID = 183
        };

        // Declare a StudentName by using an object initializer and sending
        // arguments for all three properties. No corresponding constructor is 
        // defined in the class.
        StudentName student4 = new StudentName
        {
            FirstName = "Craig",
            LastName = "Playstead",
            ID = 116
        };

        System.Console.WriteLine(student1.ToString());
        System.Console.WriteLine(student2.ToString());
        System.Console.WriteLine(student3.ToString());
        System.Console.WriteLine(student4.ToString());
    }

    // Output:
    // Craig  0
    // Craig  0
    //   183
    // Craig  116
}

public class StudentName
{
    // The default constructor has no parameters. The default constructor 
    // is invoked in the processing of object initializers. 
    // You can test this by changing the access modifier from public to 
    // private. The declarations in Main that use object initializers will 
    // fail.
    public StudentName() { }

    // The following constructor has parameters for two of the three 
    // properties. 
    public StudentName(string first, string last)
    {
        FirstName = first;
        LastName = last;
    }

    // Properties.
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }

    public override string ToString()
    {
        return FirstName + "  " + ID;
    }
}


예제

1. 다음 예제에서는 컬렉션 이니셜라이저를 사용하여 컬렉션의 StudentName 형식을 초기화하는 방법을 보여 줍니다.

2. 컬렉션 이니셜라이저는 일련의 쉼표로 구분된 개체 이니셜라이저입니다.


List<StudentName> students = new List<StudentName>()
{
  new StudentName {FirstName="Craig", LastName="Playstead", ID=116},
  new StudentName {FirstName="Shu", LastName="Ito", ID=112},
  new StudentName {FirstName="Gretchen", LastName="Rivas", ID=113},
  new StudentName {FirstName="Rajesh", LastName="Rotti", ID=114}
};




방법: 컬렉션 이니셜라이저를 사용하여 사전 초기화


1. Dictionary<TKey, TValue>는 키/값 쌍의 컬렉션을 포함합니다.

2. Add 메서드는 하나는 키 그리고 다른 하나는 값인 두 개의 매개 변수를 사용합니다.

3. Dictionary<TKey, TValue> 또는 Add 메서드가 여러 매개 변수를 사용하는 모든 컬렉션을 초기화하려면 다음 예제와 같이 각 매개 변수 집합을 괄호로 묶어야 합니다.


예제

1. 다음 코드 예제에서는 Dictionary<TKey, TValue>가 StudentName 형식의 인스턴스로 초기화됩니다.


class StudentName
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }
}

class CollInit
{
    Dictionary<int, StudentName> students = new Dictionary<int, StudentName>()
    {
        { 111, new StudentName {FirstName="Sachin", LastName="Karnik", ID=211}},
        { 112, new StudentName {FirstName="Dina", LastName="Salimzianova", ID=317}},
        { 113, new StudentName {FirstName="Andy", LastName="Ruth", ID=198}}
    };
}


2. 컬렉션의 각 요소에는 중괄호 두 쌍이 있습니다.

3. 가장 안쪽의 중괄호로 StudentName에 대한 개체 이니셜라이저를 묶고 가장 바깥쪽 중괄호로 studentsDictionary<TKey, TValue>에 추가될 키/값 쌍에 대한 이니셜라이저를 묶습니다.

4. 마지막으로 디렉터리에 대한 전체 컬렉션 이니셜라이저를 중괄호로 묶습니다.




방법: foreach를 사용하여 컬렉션 클래스 액세스


1. 다음 코드 예제에서는 foreach와 함께 사용할 수 있는 제네릭이 아닌 컬렉션 클래스의 작성 방법을 보여 줍니다.

2. 이 예제에서는 문자열 토크나이저 클래스를 정의합니다.


참고

1. 이 예제에서 보여 주는 방법은 제네릭 컬렉션 클래스를 사용할 수 없는 경우에만 사용하는 것이 좋습니다. 

2. IEnumerable<T>을 지원하는 형식이 안전한 제네릭 컬렉션 클래스를 구현하는 방법에 대한 예를 보려면 반복기(C# 및 Visual Basic)를 참조하십시오.


3. 이 예제에서 다음 코드 세그먼트는 Tokens 클래스를 사용하여 "This is a sample sentence." 문장에 대해 ' ' 및 '-'을 구분 기호로 사용하여 문장을 여러 토큰으로 분리합니다.

4. 그런 다음 foreach 문을 사용하여 토큰을 표시합니다.


Tokens f = new Tokens("This is a sample sentence.", new char[] {' ','-'});

// Display the tokens.
foreach (string item in f)
{
    System.Console.WriteLine(item);
}


예제

1. 내부적으로 Tokens 클래스에는 토큰을 저장하기 위한 배열이 사용됩니다.

2. 배열은 IEnumerator 및 IEnumerable을 구현하기 때문에 이 코드 예제에서는 Tokens 클래스에서 메서드를 정의하는 대신 배열의 열거형 메서드(GetEnumerator, MoveNext, Reset 및 Current)가 사용될 수 있었습니다.

3. 메서드 정의는 메서드의 정의 방식 및 각 메서드의 역할을 명확히 나타내기 위해 예제에 포함되었습니다.


using System.Collections;

// Declare the Tokens class. The class implements the IEnumerable interface.
public class Tokens : IEnumerable
{
    private string[] elements;

    Tokens(string source, char[] delimiters)
    {
        // The constructor parses the string argument into tokens.
        elements = source.Split(delimiters);
    }

    // The IEnumerable interface requires implementation of method GetEnumerator.
    public IEnumerator GetEnumerator()
    {
        return new TokenEnumerator(this);
    }


    // Declare an inner class that implements the IEnumerator interface.
    private class TokenEnumerator : IEnumerator
    {
        private int position = -1;
        private Tokens t;

        public TokenEnumerator(Tokens t)
        {
            this.t = t;
        }

        // The IEnumerator interface requires a MoveNext method.
        public bool MoveNext()
        {
            if (position < t.elements.Length - 1)
            {
                position++;
                return true;
            }
            else
            {
                return false;
            }
        }

        // The IEnumerator interface requires a Reset method.
        public void Reset()
        {
            position = -1;
        }

        // The IEnumerator interface requires a Current method.
        public object Current
        {
            get
            {
                return t.elements[position];
            }
        }
    }


    // Test the Tokens class.
    static void Main()
    {
        // Create a Tokens instance.
        Tokens f = new Tokens("This is a sample sentence.", new char[] {' ','-'});

        // Display the tokens.
        foreach (string item in f)
        {
            System.Console.WriteLine(item);
        }
    }
}
/* Output:
    This
    is
    a
    sample
    sentence.  
*/


4. C#에서는 컬렉션 클래스가 foreach와의 호환을 위해 IEnumerable 및 IEnumerator를 구현할 필요가 없습니다.

5. 필수 요소인 GetEnumerator, MoveNext, Reset 및 Current 멤버가 클래스에 있기만 하면 foreach를 사용할 수 있습니다.

6. 인터페이스를 생략하면 Current의 반환 형식을 Object보다 구체적으로 정의할 수 있는 이점이 있습니다.

7. 이러한 이점은 형식 안전성을 제공합니다.

8. 예를 들어, 이전 예제에서 다음 줄을 변경합니다.


// Change the Tokens class so that it no longer implements IEnumerable.
public class Tokens
{
    // . . .

    // Change the return type for the GetEnumerator method.
    public TokenEnumerator GetEnumerator()
    {   }

    // Change TokenEnumerator so that it no longer implements IEnumerator.
    public class TokenEnumerator
    {
        // . . .

        // Change the return type of method Current to string.
        public string Current
        {   }
    }
 }


9. Current에서는 문자열을 반환하므로 다음 코드와 같이 호환되지 않는 형식이 foreach 문에 사용되면 컴파일러에서 이를 감지할 수 있습니다.


// Error: Cannot convert type string to int.
foreach (int item in f)


10. IEnumerable 및 IEnumerator를 생략하면 기타 공용 언어 런타임 언어의 foreach 문이나 그에 상응하는 문에서 컬렉션 클래스를 상호 운용할 수 없다는 단점이 있습니다.




중첩 형식


1. 클래스 또는 구조체 내에서 선언된 형식을 중첩 형식이라고 합니다. 예를 들면 다음과 같습니다.


class Container
{
    class Nested
    {
        Nested() { }
    }
}


2. 중첩 형식은 외부 형식이 클래스인지 구조체인지 여부에 관계없이 기본적으로 private로 설정되지만 public, protected internal, protected, internal 또는 private로 설정할 수도 있습니다.

3. 이전 예제의 경우 Nested는 외부 형식에서 액세스할 수 없지만 다음과 같이 public으로 설정할 수 있습니다.


class Container
{
    public class Nested
    {
        Nested() { }
    }
}


4. 중첩 형식(내부 형식)은 이 형식을 포함하고 있는 형식(외부 형식)에 액세스할 수 있습니다.

5. 외부 형식에 액세스하려면 외부 형식을 중첩 형식에 생성자로 전달합니다. 예를 들면 다음과 같습니다.


public class Container
{
    public class Nested
    {
        private Container parent;

        public Nested()
        {
        }
        public Nested(Container parent)
        {
            this.parent = parent;
        }
    }
}


6. 중첩 형식은 이 형식을 포함하는 형식에 액세스할 수 있는 모든 멤버에 액세스할 수 있습니다.

7. 또한 상속 및 보호된 멤버를 포함하여 외부 형식의 개인 및 보호된 멤버에 액세스할 수 있습니다.

8. 위 선언에서 Nested 클래스의 전체 이름은 Container.Nested입니다.

9. 이 이름은 다음과 같이 중첩된 클래스의 새 인스턴스를 만드는 데 사용됩니다.


Container.Nested nest = new Container.Nested();




Partial 클래스 및 메서드


1. 클래스나 구조체, 인터페이스 또는 메서드의 정의를 둘 이상의 소스 파일로 분할할 수 있습니다.

2. 각 소스 파일에는 형식이나 메서드 정의 섹션이 들어 있고 이 모든 부분은 응용 프로그램을 컴파일할 때 결합됩니다.


Partial 클래스

1. 클래스 정의는 다음과 같은 여러 가지 상황에서 분할할 수 있습니다.

1) 대규모 프로젝트를 진행하는 경우 클래스를 개별 파일로 분할하면 여러 프로그래머가 동시에 작업을 수행할 수 있습니다.

2) 자동으로 생성된 소스를 사용하여 작업하는 경우 소스 파일을 다시 만들지 않고도 클래스에 코드를 추가할 수 있습니다. Visual Studio에서는 Windows Forms, 웹 서비스 래퍼 코드 등을 만들 때 이러한 방식을 사용합니다. 따라서 Visual Studio에서 자동으로 만든 파일을 수정할 필요 없이 이러한 클래스를 사용하는 코드를 만들 수 있습니다.

3) 클래스 정의를 분할하려면 다음과 같이 partial 키워드 한정자를 사용합니다.


public partial class Employee
{
    public void DoWork()
    {
    }
}

public partial class Employee
{
    public void GoToLunch()
    {
    }
}


2. partial 키워드는 클래스, 구조체 또는 인터페이스의 다른 부분을 네임스페이스 안에서 정의할 수 있음을 나타냅니다.

3. 모든 부분에 partial 키워드를 사용해야 합니다.

4. 최종 형식을 생성하려면 컴파일할 때 모든 부분을 사용할 수 있어야 합니다.

5. 모든 부분은 public, private 등과 같이 액세스 가능성이 동일해야 합니다.

6. 부분이 추상으로 선언되면 전체 형식이 추상으로 간주됩니다.

7. 부분이 봉인으로 선언되면 전체 형식이 봉인으로 간주됩니다.

8. 부분이 기본 형식으로 선언되면 전체 형식이 해당 클래스를 상속합니다.

9. 기본 클래스를 지정하는 모든 부분이 일치해야 하지만 기본 클래스를 생략하는 부분에서도 기본 형식을 상속합니다.

10. 부분은 서로 다른 기본 인터페이스를 지정할 수 있으며 이 경우 최종 형식에는 모든 partial 선언에 나열된 모든 인터페이스가 구현됩니다.

11. partial 정의에 선언된 모든 클래스, 구조체 또는 인터페이스 멤버를 다른 모든 부분에 사용할 수 있습니다.

12. 최종 형식은 컴파일 타임에 모든 부분의 조합이 됩니다.


참고

1. 대리자나 열거형 선언에는 partial 한정자를 사용할 수 없습니다.


13. 다음 예제에서는 중첩 형식을 포함하는 형식 자체가 partial이 아니어도 중첩 형식이 partial이 될 수 있음을 보여 줍니다.


class Container
{
    partial class Nested
    {
        void Test() { }
    }
    partial class Nested
    {
        void Test2() { }
    }
}


14. 컴파일 타임에 부분 형식(Partial Type) 정의의 특성이 병합됩니다.

15. 예를 들어, 다음 선언을 참조하십시오.


[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }


16. 이러한 선언은 다음 선언과 같습니다.


[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }


17. 다음은 모든 부분 형식(Partial Type) 정의에서 병합됩니다.

1) XML 주석

2) 인터페이스

3) 제네릭 형식 매개 변수 특성

4) 클래스 특성

5) members

예를 들어, 다음 선언을 참조하십시오.


partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }


18. 이러한 선언은 다음 선언과 같습니다.


class Earth : Planet, IRotate, IRevolve { }


제한

1. partial 클래스 정의를 사용하여 작업할 때는 다음과 같은 몇 가지 규칙을 따라야 합니다.

1) 동일한 형식의 일부를 의미하는 모든 부분 형식 정의는 partial을 사용하여 한정해야 합니다. 예를 들어, 다음 클래스 선언에서는 오류가 발생합니다.


public partial class A { }
//public class A { }  // Error, must also be marked partial


2) partial 한정자는 class, struct 또는 interface 키워드 바로 앞에만 사용할 수 있습니다.

3) 다음 예제와 같이 중첩된 부분 형식(Partial Type)은 부분 형식(Partial Type) 정의에 사용할 수 있습니다.


partial class ClassWithNestedClass
{
    partial class NestedClass { }
}

partial class ClassWithNestedClass
{
    partial class NestedClass { }
}


4) 동일한 형식의 일부를 의미하는 모든 부분 형식(Partial Type) 정의는 동일한 어셈블리와 동일한 모듈(.exe 또는 .dll 파일)에서 정의해야 합니다. partial 정의는 여러 모듈에 분산될 수 없습니다.

5) 클래스 이름과 제네릭 형식 매개 변수는 모든 부분 형식(Partial Type) 정의에서 일치해야 합니다. 제네릭 형식은 부분 형식(Partial Type)이 될 수 있습니다. 각 partial 선언에서는 동일한 매개 변수 이름을 동일한 순서대로 사용해야 합니다.

6) 부분 형식(Partial Type) 정의에 대한 다음 키워드는 선택적이지만 부분 형식(Partial Type) 정의 하나에 이러한 키워드가 있으면 동일한 형식의 다른 partial 정의에 지정된 키워드와 충돌하지 말아야 합니다.

public, private, protected, internal, abstract, sealed, 기본 클래스, new 한정자(중첩된 부분), 제네릭 제약 조건, 자세한 내용은 형식 매개 변수에 대한 제약 조건(C# 프로그래밍 가이드)를 참조하십시오.


예제 1


설명

1. 다음 예제에서는 CoOrds 클래스의 필드와 생성자를 partial 클래스 정의 하나에서 선언하고 PrintCoOrds 멤버를 다른 partial 클래스 정의에서 선언합니다.


public partial class CoOrds
{
    private int x;
    private int y;

    public CoOrds(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

public partial class CoOrds
{
    public void PrintCoOrds()
    {
        Console.WriteLine("CoOrds: {0},{1}", x, y);
    }

}

class TestCoOrds
{
    static void Main()
    {
        CoOrds myCoOrds = new CoOrds(10, 15);
        myCoOrds.PrintCoOrds();

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: CoOrds: 10,15


예제 2


설명

1. 다음 예제에서는 partial 구조체와 인터페이스를 개발하는 방법을 보여 줍니다.


partial interface ITest
{
    void Interface_Test();
}

partial interface ITest
{
    void Interface_Test2();
}

partial struct S1
{
    void Struct_Test() { }
}

partial struct S1
{
    void Struct_Test2() { }
}


부분 메서드(Partial Method)

1. partial 클래스 또는 구조체는 부분 메서드(Partial Method)를 포함할 수 있습니다.

2. 클래스의 한 부분에는 메서드 시그니처가 포함됩니다.

3. 같은 부분이나 다른 부분에 선택적 구현을 정의할 수 있습니다.

4. 구현을 정의하지 않으면 컴파일할 때 메서드 및 메서드에 대한 모든 호출이 제거됩니다.

5. 클래스의 한 부분을 구현하는 사용자는 부분 메서드를 사용하여 이벤트와 비슷한 메서드를 정의할 수 있습니다.

6. 이 경우 클래스의 다른 부분을 구현하는 사용자는 해당 메서드를 구현할지 여부를 결정할 수 있습니다.

7. 메서드를 구현하지 않을 경우, 컴파일러에서 메서드 시그니처와 메서드에 대한 모든 호출을 제거합니다.

8. 호출의 인수 평가 시 발생할 수 있는 결과를 포함하여 메서드에 대한 호출은 런타임 시 영향이 없습니다.

9. 따라서 partial 클래스의 코드에는 구현을 정의하지 않고도 부분 메서드를 마음대로 사용할 수 있습니다.

10. 메서드를 호출만 하고 구현하지 않더라도 컴파일 타임 오류나 런타임 오류가 발생하지 않습니다.

11. 부분 메서드는 생성된 코드를 사용자 지정할 때 특히 유용합니다.

12. 부분 메서드를 사용하면 메서드 이름과 시그니처를 예약할 수 있습니다.

13. 이렇게 하면 생성된 코드에서 메서드를 호출해도 메서드를 구현할지 여부를 개발자가 결정할 수 있습니다.

14. partial 클래스와 마찬가지로 부분 메서드를 사용해도 별도의 런타임 비용 없이 코드 생성기에서 만든 코드와 개발자가 직접 만든 코드를 함께 사용할 수 있습니다.

15. 부분 메서드 선언은 메서드 정의와 메서드 구현이라는 두 부분으로 구성됩니다.

16. 이 두 부분은 partial 클래스에서 각각 별도의 부분에 포함되거나 동일한 부분에 포함될 수 있습니다.

17. 구현을 선언하지 않으면 컴파일러에서 정의하는 선언 및 메서드에 대한 모든 호출을 최적화합니다.


// Definition in file1.cs

partial void onNameChanged();


// Implementation in file2.cs

partial void onNameChanged()

{

  // method body

}


1) 부분 메서드 선언은 partial 키워드로 시작해야 하고 메서드는 void를 반환해야 합니다.

2) 부분 메서드에는 ref 매개 변수만 사용할 수 있고 out 매개 변수는 사용할 수 없습니다.

3) 부분 메서드는 암시적으로 private이므로 virtual이 될 수 없습니다.

4) 부분 메서드는 본문이 있는지 여부에 따라 정의 부분인지 구현 부분인지가 결정되기 때문에 extern이 될 수 없습니다.

5) 부분 메서드에는 static 및 unsafe 한정자를 사용할 수 있습니다.

6) 부분 메서드는 제네릭 메서드일 수 있습니다. 제약 조건은 정의하는 부분 메서드 선언에 포함되며, 필요한 경우 구현하는 부분 메서드 선언에 반복될 수 있습니다. 매개 변수 및 형식 매개 변수 이름은 구현하는 선언과 정의하는 선언에서 동일하지 않아도 됩니다.

7) 대리자를 정의 및 구현된 부분 메서드에 대해 만들 수 있지만 단지 정의된 부분 메서드에 대해서는 만들 수 없습니다.




익명 형식


1. 익명 형식을 사용하면 먼저 명시적으로 형식을 정의할 필요 없이 읽기 전용 속성 집합을 단일 개체로 편리하게 캡슐화할 수 있습니다.

2. 형식 이름은 컴파일러에 의해 생성되며 소스 코드 수준에서 사용할 수 없습니다.

3. 각 속성의 형식은 컴파일러에서 유추합니다.

4. new 연산자를 개체 이니셜라이저와 함께 사용하여 익명 형식을 만듭니다.

5. 개체 이니셜라이저에 대한 자세한 내용은 개체 및 컬렉션 이니셜라이저(C# 프로그래밍 가이드)를 참조하세요.

6. 다음 예제에서는 Amount 및 Message라는 두 속성으로 초기화된 익명 형식을 보여 줍니다.


var v = new { Amount = 108, Message = "Hello" };

// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);


7. 일반적으로 익명 형식은 소스 시퀀스에 있는 각 개체의 속성 하위 집합을 반환하기 위해 쿼리 식의 select 절에 사용됩니다.

8. 쿼리에 대한 자세한 내용은 LINQ 쿼리 식(C# 프로그래밍 가이드)를 참조하십시오.

9. 익명 형식은 하나 이상의 public 읽기 전용 속성을 포함합니다.

10. 메서드 또는 이벤트와 같은 다른 종류의 클래스 멤버는 유효하지 않습니다.

11. 속성을 초기화하는 데 사용되는 식은 null, 익명 함수 또는 포인터 형식일 수 없습니다.

12. 가장 일반적인 시나리오는 다른 형식의 속성으로 익명 형식을 초기화하는 것입니다.

13. 다음 예제에서는 Product라는 클래스가 있다고 가정합니다.

14. Product 클래스에는 Color 및 Price 속성뿐만 아니라 관심 없는 다른 속성도 포함되어 있습니다.

15. products 변수는 Product 개체의 컬렉션입니다.

16. 익명 형식 선언은 new 키워드로 시작합니다.

17. 선언에서는 Product의 두 속성만 사용하는 새 형식을 초기화합니다.

18. 따라서 쿼리에 작은 양의 데이터가 반환됩니다.

19. 익명 형식에 멤버 이름을 지정하지 않으면 컴파일러가 익명 형식 멤버에 해당 멤버를 초기화하는 데 사용된 속성과 동일한 이름을 제공합니다.

20. 앞의 예제에 표시된 것처럼, 식으로 초기화되는 속성의 이름을 제공해야 합니다.

21. 다음 예제에서 익명 형식의 속성 이름은 Color 및 Price입니다.


var productQuery = 
    from prod in products
    select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}


22. 일반적으로 익명 형식을 사용하여 변수를 초기화할 때는 var을 사용하여 변수를 암시적으로 형식화된 지역 변수로 선언합니다.

23. 컴파일러만 익명 형식의 기본 이름에 액세스할 수 있으므로 변수 선언에는 형식 이름을 지정할 수 없습니다.

24. var에 대한 자세한 내용은 암시적으로 형식화된 지역 변수(C# 프로그래밍 가이드)을 참조하십시오.

25. 다음 예제에 표시된 것처럼, 암시적으로 형식화된 지역 변수와 암시적으로 형식화된 배열을 결합하여 익명으로 형식화된 요소의 배열을 만들 수 있습니다.


var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};


설명

1. 익명 형식은 object에서 직접 파생되고 object를 제외한 모든 형식으로 캐스팅될 수 없는 class 형식입니다.

2. 컴파일러는 응용 프로그램에서 해당 익명 형식에 액세스할 수 없더라도 각 익명 형식의 이름을 제공합니다.

3. 공용 언어 런타임의 관점에서 익명 형식은 다른 참조 형식과 다를 바가 없습니다.

4. 어셈블리에서 둘 이상의 익명 개체 이니셜라이저가 순서와 이름 및 형식이 동일한 속성의 시퀀스를 지정하는 경우 컴파일러는 개체를 동일한 형식의 인스턴스로 처리합니다.

5. 이러한 개체는 컴파일러에서 생성된 동일한 형식 정보를 공유합니다.

6. 익명 형식을 가지고 있으므로 필드, 속성, 이벤트 또는 메서드의 반환 형식은 선언할 수 없습니다.

7. 마찬가지로, 익명 형식을 가지고 있으므로 메서드, 속성, 생성자 또는 인덱서의 정식 매개 변수는 선언할 수 없습니다.

8. 익명 형식이나 익명 형식을 포함한 컬렉션을 메서드에 대한 인수로 전달하려면 매개 변수를 형식 개체로 선언하면 됩니다.

9. 그러나 이렇게 하면 강력한 형식화를 사용하는 의미가 없습니다.

10. 쿼리 결과를 저장하거나 메서드 경계 외부로 전달해야 하는 경우 익명 형식 대신 일반적인 명명된 구조체 또는 클래스 사용을 고려하세요.

11. 익명 형식에 대한 Equals 및 GetHashCode 메서드는 속성의 Equals 및 GetHashCode 메서드 측면에서 정의되므로 동일한 익명 형식의 두 인스턴스는 해당 속성이 모두 동일한 경우에만 동일합니다.



방법: 쿼리에서 요소 속성의 하위 집합 반환


1. 이러한 조건이 모두 적용되는 경우 쿼리 식에서 익명 형식을 사용합니다.

1) 각 소스 개체의 일부 속성만 반환할 수 있습니다.

2) 쿼리가 실행되는 메서드의 범위 외부에 쿼리 결과를 저장할 필요가 없습니다.

2. 각 소스 개체에서 하나의 속성 또는 필드를 반환하기만 하려는 경우 select 절에서 도트 연산자를 사용할 수 있습니다.

3. 예를 들어 각 student의 ID만 반환하려면 다음과 같이 select 절을 작성하십시오.


select student.ID;


예제

1. 다음 예제에서는 익명 형식을 사용하여 지정된 조건과 일치하는 각 소스 개체의 속성 하위 집합만 반환하는 방법을 보여 줍니다.


private static void QueryByScore()
{
    // Create the query. var is required because
    // the query produces a sequence of anonymous types.
    var queryHighScores =
        from student in students
        where student.ExamScores[0] > 95
        select new { student.FirstName, student.LastName };

    // Execute the query.
    foreach (var obj in queryHighScores)
    {
        // The anonymous type's properties were not named. Therefore 
        // they have the same names as the Student properties.
        Console.WriteLine(obj.FirstName + ", " + obj.LastName);
    }
}
/* Output:
Adams, Terry
Fakhouri, Fadi
Garcia, Cesar
Omelchenko, Svetlana
Zabokritski, Eugene
*/


2. 익명 형식은 이름이 지정되지 않은 경우 해당 속성에 대한 소스 요소의 이름을 사용합니다.

3. 익명 형식의 속성에 새 이름을 부여하려면 다음과 같이 select 문을 작성하십시오.


select new { First = student.FirstName, Last = student.LastName };


4. 앞의 예제에서 이를 시도할 경우에는 Console.WriteLine 문도 변경해야 합니다.


Console.WriteLine(student.First + " " + student.Last);


















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

파일 시스템 및 레지스트리  (0) 2016.08.25
형식 참조 테이블  (0) 2016.08.23
인터페이스, 이벤트, 인덱서  (0) 2016.08.04
제네릭 요약  (0) 2016.08.02
LINQ 정리  (1) 2016.07.28
:
Posted by 지훈2