2016년 2월 18일 목요일

C# 프로그래밍 가이드 중 일부 발췌 - 배열, 클래스 및 구조체

개인적인 정리입니다. 글을 보시기 보다는 아래 링크에 방문하세요.
https://msdn.microsoft.com/ko-kr/library/67ef8sbd.aspx

배열

https://msdn.microsoft.com/ko-kr/library/9b9dty7d.aspx
  • 숫자 배열 요소에는 0이, 참조 요소에는 null이 기본값으로 설정됩니다.
  • 가변 배열은 배열의 배열이므로, 각 요소는 참조 형식이고 null로 초기화됩니다.

클래스 및 구조체

클래스는 참조 형식입니다.클래스의 개체를 만드는 경우 개체가 할당된 변수에는 해당 메모리에 대한 참조만 포함됩니다.개체 참조가 다른 변수에 할당되면 새 변수는 원래 개체를 참조합니다.두 변수는 동일한 데이터를 참조하므로 한 변수를 통해 변경된 내용은 다른 변수에도 적용됩니다.
구조체는 값 형식입니다.구조체를 만드는 경우 구조체가 할당된 변수에는 구조체의 실제 데이터가 포함됩니다.구조체를 새 변수에 할당하면 해당 구조체가 복사되므로새 변수와 원래 변수는 동일한 데이터의 별도 복사본을 보유하게 됩니다.한 복사본이 변경되어도 다른 복사본은 영향을 받지 않습니다.
일반적으로 클래스는 클래스 개체를 생성한 후 수정하려고 하는 데이터나 더 복잡한 동작을 모델링하는 데 사용됩니다.구조체는 구조체를 생성한 후 수정하지 않을 데이터를 주로 포함하는 작은 데이터 구조에 더 적합합니다. 

모든 메서드, 필드, 상수, 속성 및 이벤트는 형식 내에서 선언되어야 하는데 이들을 형식의 멤버라고 합니다.C#에는 일부 다른 언어와 달리 전역 변수 또는 메서드가 없습니다.프로그램의 진입점인 Main 메서드까지도 클래스 또는 구조체 내부에서 선언해야 합니다. 

구조체

구조체는 클래스와 동일한 구문을 대부분 공유하지만 클래스보다 제한적입니다.
  • 구조체 선언 내에서 필드는 const 또는 static으로 선언한 경우에만 초기화할 수 있습니다.
  • 구조체에서는 매개 변수가 없는 생성자인 기본 생성자나 소멸자를 선언할 수 없습니다.
  • 구조체는 할당 시 복사됩니다. 구조체가 새 변수에 할당되면 모든 데이터가 복사되고, 새 복사본을 수정해도 원래 복사본의 데이터는 변경되지 않습니다. Dictionary<string, myStruct>와 같은 값 형식의 컬렉션을 사용할 때 이것이 중요합니다.
  • 구조체는 값 형식이고 클래스는 참조 형식입니다.
  • 클래스와 달리 구조체는 new 연산자를 사용하지 않고 인스턴스화할 수 있습니다.
  • 구조체는 매개 변수가 있는 생성자를 선언할 수 있습니다.
  • 구조체는 다른 구조체 또는 클래스에서 상속될 수 없으며, 클래스의 기본 클래스가 될 수도 없습니다. 모든 구조체는 System.Object를 상속하는 System.ValueType에서 직접 상속합니다.
  • 구조체는 인터페이스를 구현할 수 있습니다.
  • 구조체를 nullable 형식으로 사용할 수 있고 여기에 null 값을 할당할 수 있습니다. 

상속

https://msdn.microsoft.com/ko-kr/library/ms173149.aspx
 클래스는 자신 또는 멤버를 sealed로 선언하여 다른 클래스가 상속하는 것을 막을 수 있습니다. 자세한 내용은 추상 및 봉인 클래스와 클래스 멤버(C# 프로그래밍 가이드)를 참조하십시오.

다형성

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

파생 클래스는 기본 클래스 멤버가 virtual 또는 abstract로 선언된 경우에만 기본 클래스 멤버를 재정의할 수 있습니다. 파생 멤버는 override 키워드를 사용하여 멤버가 가상 호출에 참여하도록 되어 있음을 명시적으로 나타내야 합니다. 다음 코드는 예제를 제공합니다.
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; }
    }
}


파생 멤버가 기본 클래스의 멤버와 동일한 이름을 갖되 가상 호출에는 참여하지 않도록 하려면 new 키워드를 사용하면 됩니다. new 키워드는 바꿀 클래스 멤버의 반환 형식 앞에 배치됩니다. 다음 코드는 예제를 제공합니다.
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; }
    }
}


파생 클래스는 재정의를 sealed로 선언하여 가상 상속을 중지할 수 있습니다. 이렇게 하려면 클래스 멤버 선언에서override 키워드 앞에 sealed 키워드를 배치해야 합니다. 다음 코드는 예제를 제공합니다.
public class C : B
{
    public sealed override void DoWork() { }
}

https://msdn.microsoft.com/ko-KR/library/6fawty39.aspx

C#에서는 파생 클래스에 기본 클래스 메서드와 이름이 동일한 메서드를 포함할 수 있습니다.
  • 기본 클래스 메서드는 virtual로 정의해야 합니다.
  • 파생 클래스의 메서드 앞에 new 또는 override 키워드를 사용하지 않으면 컴파일러에서 경고가 발생하고 메서드에 new 키워드가 있는 것처럼 작동합니다.
  • 파생 클래스의 메서드 앞에 new 키워드를 사용하면 메서드가 기본 클래스의 메서드에 종속되지 않은 것으로 정의됩니다.
  • 파생 클래스의 메서드 앞에 override 키워드를 사용하면 파생 클래스의 개체에서 기본 클래스 메서드 대신 이 메서드를 호출합니다.
  • 기본 클래스 메서드는 base 키워드를 사용하여 파생 클래스 내에서 호출할 수 있습니다.
  • override, virtual  new 키워드는 속성, 인덱서 및 이벤트에도 적용할 수 있습니다.

추상(abstract) 및 봉인(sealed) 클래스와 클래스 멤버

https://msdn.microsoft.com/ko-KR/library/ms173150.aspx

// 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.
    }
}
virtual 메서드는 abstract로 선언되어도 추상 클래스에서 상속된 모든 클래스에 대해 여전히 가상입니다.추상 메서드를 상속하는 클래스에서는 메서드의 원본 구현에 액세스할 수 없습니다. 앞의 예제에서 F 클래스의 DoWork에서는 D 클래스의 DoWork를 호출할 수 없습니다.따라서 추상 클래스는 파생 클래스에서 가상 메서드에 대한 새 메서드 구현을 반드시 제공하도록 제한할 수 있습니다.

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

https://msdn.microsoft.com/ko-KR/library/79b3xss3.aspx  
모든 클래스 형식의 경우와 같이 정적 클래스의 형식 정보는 이 클래스를 참조하는 프로그램이 로드될 때 .NET Framework CLR(공용 언어 런타임)에 의해 로드됩니다. 프로그램에서는 클래스가 로드되는 정확한 시점을 지정할 수 없습니다.하지만 프로그램에서 클래스를 처음 참조하기 전에 이 클래스는 반드시 로드되고 해당 필드가 초기화되고 정적 생성자가 호출됩니다.정적 생성자는 한 번만 호출되며 정적 클래스는 프로그램이 속한 응용 프로그램 도메인의 수명 기간 동안 메모리에 유지됩니다.  
필드를 static const로 선언할 수는 없지만 const 필드가 보여 주는 동작은 본질적으로 정적입니다.const 필드는 형식에 속해 있지 형식의 인스턴스에 속해 있지 않습니다.
==> 뭔소리야? 영어 원문을 봐도 이해 안됨.   

액세스 한정자

https://msdn.microsoft.com/ko-KR/library/ms173121.aspx  
internal
동일한 어셈블리의 코드에서는 형식이나 멤버에 액세스할 수 있지만 다른 어셈블리의 코드에서는 액세스할 수 없습니다.
protected internal
형식 또는 멤버는 선언되는 어셈블리의 모든 코드에 액세스하거나 다른 어셈블리의 파생 클래스 내에서 액세스할 수 있습니다. 다른 어셈블리로부터의 액세스는, 반드시 protected internal 요소가 선언된 클래스에서 파생한 클래스 선언 내에서 발생해야 하며, 또한 반드시 이렇게 파생된 클래스 형식의 인스턴스를 통해서 발생해야 합니다.

상수

https://msdn.microsoft.com/ko-kr/library/ms173119.aspx
클래스, 구조체, 배열을 비롯한 사용자 정의 형식은 const가 될 수 없습니다. 런타임에 생성자 내부 등에서 한 번 초기화되고 그 이후로는 변경할 수 없는 클래스, 구조체 또는 배열을 만들려면 readonly 한정자를 사용하십시오.  
DLL과 같은 다른 코드에 정의된 상수 값을 참조할 때는 매우 주의해야 합니다. DLL 새 버전에서 해당 상수에 대해 새로운 값을 정의하는 경우 사용자 프로그램에서는 새 버전을 사용하여 다시 컴파일하기 전까지 이전 리터럴 값을 보유하게 됩니다. 

속성

https://msdn.microsoft.com/ko-kr/library/x9fsa0sw.aspx  
일반적으로 식의 결과와 함께 바로 반환되는 속성이 있습니다. => 를 사용하여 이러한 속성을 정의하기 위한 구문 바로 가기는 다음과 같습니다.
public string Name => First + " " + Last; 
속성은 읽기 전용이어야 하며, get 접근자 키워드를 사용하지 않습니다.

클래스의 구현자에게 있어 속성은 get 접근자 및/또는 set 접근자를 나타내는 한 개 또는 두 개의 코드 블록입니다. get 접근자의 코드 블록은 속성을 읽을 때 실행되고, set 접근자의 코드 블록은 속성에 새 값을 할당할 때 실행됩니다. set 접근자가 없는 속성은 읽기 전용으로 간주됩니다. get 접근자가 없는 속성은 쓰기 전용으로 간주됩니다. 두 접근자가 모두 있는 속성은 읽고 쓸 수 있습니다. 

class Person
{
    private string name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}

https://msdn.microsoft.com/ko-kr/library/75e8y5dd.aspxd

private string name = "Hello";

public string Name
{
    get
    {
        return name;
    }
    protected set
    {
        name = value;
    }
}

속성이나 인덱서에 대한 접근자 한정자를 사용하려면 다음과 같은 조건을 준수해야 합니다.
  • 인터페이스나 명시적 인터페이스 멤버 구현에는 접근자 한정자를 사용할 수 없습니다.
  • 접근자 한정자는 속성이나 인덱서에 set  get 접근자가 모두 있는 경우에만 사용할 수 있습니다. 이 경우 한정자는 두 접근자 중 하나에 대해서만 허용됩니다.
  • 속성이나 인덱서에 재정의 한정자가 있는 경우 재정의된 접근자의 접근자가 있으면 접근자 한정자가 이와 일치해야 합니다.
  • 접근자에 대한 액세스 수준은 속성이나 인덱서 자체에 대한 액세스 수준보다 더 제한적이어야 합니다.

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

다음 예제에서는 자동 구현 속성을 갖는 변경할 수 없는 클래스를 구현하는 두 가지 방법을 보여 줍니다. 각 방법에서 속성 중 하나는 private set으로 선언하고 다른 하나는 get으로만 선언합니다. 첫 번째 클래스는 생성자만 사용하여 속성을 초기화하고 두 번째 클래스는 생성자를 호출하는 정적 팩터리 메서드를 사용합니다.
// 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;               
        }
    }

메서드

기본적으로 값 형식이 메서드에 전달될 때 개체 자체가 아닌 복사본이 전달됩니다.따라서 인수에 대한 변경 내용은 호출하는 메서드의 원래 복사본에 영향을 주지 않습니다.ref 키워드를 사용하여 값 형식을 참조로 전달할 수 있습니다.자세한 내용은 값 형식 매개 변수 전달(C# 프로그래밍 가이드)을 참조하세요.기본 제공 값 형식 목록을 보려면값 형식 표(C# 참조)를 참조하세요.
참조 형식의 개체가 메서드에 전달될 때 개체에 대한 참조가 전달됩니다.즉, 메서드는 개체 자체가 아니라 개체의 위치를 나타내는 인수를 수신합니다.이 참조를 사용하여 개체의 멤버를 변경하면 개체를 값으로 전달하더라도 변경 내용은 호출하는 메서드의 인수에 반영됩니다.
> 값 형식 매개 변수 전달
 : https://msdn.microsoft.com/ko-KR/library/9t0za5es.aspx
값 형식 변수는 데이터에 대한 참조를 포함하는 참조 형식 변수와는 반대로 데이터를 직접 포함합니다. 메서드에 값-형식 변수를 값으로 전달 변수의 복사본을 전달 하는 것을 의미 합니다. 메서드에서 변수 변경 매개 변수 없는 인수 변수에 저장할 원본 데이터에 영향을 주지. 호출된 된 메서드의 매개 변수 값을 변경 하려면 참조로 전달 해야 합니다 사용 하는 ref 또는 out 키워드.

> 참조 형식 매개 변수 전달
 :  https://msdn.microsoft.com/ko-KR/library/s6938f28.aspx
참조 형식 변수는 데이터를 직접 포함하지 않고 데이터에 대한 참조를 포함합니다. 값으로 참조-형식 매개 변수를 전달할 경우 클래스 멤버 값과 같은 참조에서 가리키는 데이터를 변경할 수 있으나 참조 값 자체를 변경할 수는 없습니다. 즉, 같은 참조를 사용하여 새 클래스에 메모리를 할당하고 이를 블록 밖에서 지속하도록 할 수 없습니다. 그렇게 하려면 ref 또는 out 키워드를 사용하여 매개 변수를 전달해야 합니다.


비동기 기능을 사용하면 명시적 콜백을 사용하거나 수동으로 여러 메서드 또는 람다 식에 코드를 분할하지 않고도 비동기 메서드를 호출할 수 있습니다.비동기 기능은 Visual Studio 2012에서 도입되었습니다.
메서드에 async 한정자를 표시하면 메서드에서 await 연산자를 사용할 수 있습니다.컨트롤이 비동기 메서드의 await 식에 도달하면 컨트롤이 호출자로 돌아가고 대기 중인 작업이 완료될 때까지 메서드의 진행이 일시 중단됩니다.작업이 완료되면 메서드가 실행이 다시 시작될 수 있습니다.
// using System.Diagnostics;
// using System.Threading.Tasks;

// This Click event is marked with the async modifier.
private async void startButton_Click(object sender, RoutedEventArgs e)
{
    await DoSomethingAsync();
}

private async Task DoSomethingAsync()
{
    Task<int> delayTask = DelayAsync();
    int result = await delayTask;

    // The previous two statements may be combined into
    // the following statement.
    //int result = await DelayAsync();

    Debug.WriteLine("Result: " + result);
}

private async Task<int> DelayAsync()
{
    await Task.Delay(100);
    return 5;
}

// Output:
//  Result: 5

 var 키워드는 "variant"를 의미하지 않으며 변수가 느슨한 형식이거나 런타임에 바인딩됨을 나타내지 않습니다. 이 키워드는 단지 컴파일러에서 가장 적합한 형식을 결정하여 할당한다는 것을 의미합니다.
암시적으로 형식화된 선언에는 다음과 같은 제한 사항이 있습니다.
  • var은 동일한 문에서 지역 변수를 선언하고 초기화하는 경우에만 사용할 수 있습니다. 변수를 null, 메서드 그룹 또는 익명 함수로 초기화할 수 없습니다.
  • var은 클래스 범위의 필드에서 사용할 수 없습니다.
  • var을 사용하여 선언된 변수는 초기화 식에서 사용할 수 없습니다.  : int i = (i = 20); 식은 문제가 없지만 이 식에서는 컴파일 타임 오류인 var i = (i = 20);가 발생합니다.
  • 암시적으로 형식화된 여러 변수를 동일한 문에서 초기화할 수 없습니다.
  • var이라는 이름의 형식이 범위에 있으면 var 키워드는 해당 형식 이름으로 확인되며 암시적으로 형식화된 지역 변수 선언의 일부로 처리되지 않습니다.

명명된 인수를 사용하면 호출되는 메서드의 매개 변수 목록에 있는 매개 변수의 순서를 기억하거나 확인할 필요가 없게 됩니다. 각 인수에 대한 매개 변수를 지정하려면 매개 변수 이름을 사용하면 됩니다. 예를 들어, BMI(체질량 지수)를 계산하는 함수의 경우 해당 함수에 정의된 순서에 따라 체중 및 높이에 대한 인수를 차례로 지정하여 전달하는 표준적인 방법으로 호출될 수 있습니다.
CalculateBMI(123, 64);
매개 변수 순서를 기억하지 못하거나 매개 변수 이름을 모르면 체중 및 높이에 대한 인수를 순서와 상관없이 보낼 수 있습니다.
CalculateBMI(weight: 123, height: 64);
CalculateBMI(height: 64, weight: 123);
명명된 인수를 사용하면 각 인수가 무엇을 나타내는지 확인할 수 있으므로 코드 가독성도 향상됩니다.
명명된 인수는 다음과 같이 위치 인수 뒤에 올 수 있습니다.
CalculateBMI(123, height: 64);
그러나 위치 인수가 명명된 인수 다음에 올 수는 없습니다. 다음 문은 컴파일러 오류를 발생시킵니다.
//CalculateBMI(weight: 123, 64);

생성자

생성자에서는 base 키워드를 사용하여 기본 클래스의 생성자를 호출할 수 있습니다.예를 들면 다음과 같습니다.
public class Manager : Employee
{
    public Manager(int annualSalary)
        : base(annualSalary)
    {
        //Add further instructions here.
    }
}

정적 생성자에는 다음과 같은 속성이 있습니다.
  • 정적 생성자는 액세스 한정자를 사용하지 않고 매개 변수를 갖지 않습니다.
  • 정적 생성자는 첫 번째 인스턴스가 만들어지기 전이나 정적 멤버가 참조되기 전에 클래스를 초기화하기 위해 자동으로 호출됩니다.
  • 정적 생성자는 직접 호출할 수 없습니다.
  • 사용자는 프로그램에서 정적 생성자가 실행되는 시기를 제어할 수 없습니다.
  • 정적 생성자는 일반적으로 클래스에서 로그 파일을 사용할 때 이 파일에 항목을 쓰기 위해 사용됩니다.
  • 정적 생성자는 생성자에서 LoadLibrary 메서드를 호출할 수 있는 경우 비관리 코드에 대한 래퍼 클래스를 만들 때도 유용합니다.
  • 정적 생성자에서 예외를 throw하면 런타임은 다시 이 생성자를 호출하지 않으므로 해당 형식은 프로그램이 실행되는 응용 프로그램 도메인의 수명 동안 초기화되지 않은 상태로 남아 있게 됩니다.


소멸자

  • 소멸자는 구조체에 정의할 수 없습니다. 이것은 클래스와만 사용할 수 있습니다.
  • 클래스는 하나의 소멸자만 가질 수 있습니다.
  • 소멸자는 상속되거나 오버로드될 수 없습니다.
  • 소멸자는 사용자가 호출할 수 없으며, 자동으로 호출됩니다.
  • 소멸자는 한정자를 사용하거나 매개 변수를 갖지 않습니다. 

    빈 소멸자는 사용하지 않는 것이 좋습니다. 클래스에 소멸자가 포함된 경우 Finalize 큐에 엔트리가 만들어집니다. 소멸자가 호출되면 큐 처리를 위해 가비지 수집기가 호출됩니다. 그러므로 빈 소멸자는 성능 저하를 가져올 뿐입니다.
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.
*/

개체 및 컬렉션 이니셜라이저

  class Cat
{
    // Auto-implemented properties.
    public int Age { get; set; }
    public string Name { get; set; }
}
Cat cat = new Cat { Age = 10, Name = "Fluffy" };
 var pet = new { Age = 10, Name = "Fluffy" };
var productInfos =
    from p in products
    select new { p.ProductName, p.UnitPrice };
 foreach(var p in productInfos){...}

 개체 이니셜라이저에 nullable 구조체를 사용하는 것은 컴파일 타임 오류입니다.

Partial 클래스 및 메서드

  • 클래스 정의를 분할하려면 다음과 같이 partial 키워드 한정자를 사용합니다.
public partial class Employee
{
    public void DoWork()
    {
    }
}

public partial class Employee
{
    public void GoToLunch()
    {
    }
}
partial 키워드는 클래스, 구조체 또는 인터페이스의 다른 부분을 네임스페이스 안에서 정의할 수 있음을 나타냅니다.모든 부분에 partial 키워드를 사용해야 합니다.최종 형식을 생성하려면 컴파일할 때 모든 부분을 사용할 수 있어야 합니다.모든 부분은 public, private 등과 같이 액세스 가능성이 동일해야 합니다.

 partial 클래스 또는 구조체는 부분 메서드(Partial Method)를 포함할 수 있습니다.클래스의 한 부분에는 메서드 시그니처가 포함됩니다.같은 부분이나 다른 부분에 선택적 구현을 정의할 수 있습니다.

부분 메서드 선언은 메서드 정의와 메서드 구현이라는 두 부분으로 구성됩니다.이 두 부분은 partial 클래스에서 각각 별도의 부분에 포함되거나 동일한 부분에 포함될 수 있습니다.구현을 선언하지 않으면 컴파일러에서 정의하는 선언 및 메서드에 대한 모든 호출을 최적화합니다.
// Definition in file1.cs
partial void onNameChanged();

// Implementation in file2.cs
partial void onNameChanged()
{
  // method body
}
  • 부분 메서드 선언은 partial 키워드로 시작해야 하고 메서드는 void를 반환해야 합니다.
  • 부분 메서드에는 ref 매개 변수만 사용할 수 있고 out 매개 변수는 사용할 수 없습니다.
  • 부분 메서드는 암시적으로 private이므로 virtual이 될 수 없습니다.
  • 부분 메서드는 본문이 있는지 여부에 따라 정의 부분인지 구현 부분인지가 결정되기 때문에 extern이 될 수 없습니다.
  • 부분 메서드에는 static  unsafe 한정자를 사용할 수 있습니다.
  • 부분 메서드는 제네릭 메서드일 수 있습니다.제약 조건은 정의하는 부분 메서드 선언에 포함되며, 필요한 경우 구현하는 부분 메서드 선언에 반복될 수 있습니다.매개 변수 및 형식 매개 변수 이름은 구현하는 선언과 정의하는 선언에서 동일하지 않아도 됩니다.
  • 대리자를 정의 및 구현된 부분 메서드에 대해 만들 수 있지만 단지 정의된 부분 메서드에 대해서는 만들 수 없습니다.

댓글 없음:

댓글 쓰기