?. 연산자 (null 조건부 연산자)



 null 조건 연산자는 ? 앞에 있는 객체가 NULL인지 체크해서


1. NULL 이라면, NULL을 리턴.

2. NULL이 아니라면, ? 다음의 속성이나 메서드를 실행.


일반적으로 ?. 와 같이 표현되지만, 만약 인덱서 혹은 배열 요소에 적근할 경우는 ?[]와 같이 표현된다.


1
2
3
4
5
6
7
8
9

// customer 컬렉션이 null 이면 cnt는 null
// null이 아니라면, cnt는 customer의 갯수.
int? cnt = customer?.Count;
 
// customer가 null인지 체크하고
// 다시 customer[0]가 null인지 체크한다.
// customer이 null이거나 customer[0]이 null이라면 age는 null
// 그렇지 않다면, age는 customer[0].age
int? age = customer?[0]?.age;

cs



null 조건부 연산자를 사용하면 null 참조될 수 있는 객체에 안전하게 접근할 수 있으며,

null에 대한 예외 처리를 축약해서 개발자의 의도를 표현할 수 있다.






참  고: http://www.csharpstudy.com/CS6/CSharp-null-conditional-operator.aspx


캐스트보다는 is, as가 좋다.



[ Content ]

cast (캐스트)
    . 프로그래밍 언어에서, 객체의 유형을 다른 유형으로 바꾸는 것을 말한다.
    . 아래는 C형태의 캐스트로 '(타입)피연산자' 형태로 사용된다.


1
2
3
int i = 42;
char* p = &buf;
*p = (char)i;

cs



- as 연산자

    . 특정 형식의 변환을 수행한다. 캐스트 작업과 비슷하지만, 변환할 수 없는 경우 예외를 발생시키지 않고 null을 반환한다.
    . 참조 : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/as


is 연산자

    . 지정된 형식으로 변환할 수 있는지, 런타임에 형식 호환성을 평가한다. 
      변환이 가능하면 true, 그렇지 않으면 false를 반환한다.
    . 참조 : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/is


- is, as 연산자를 사용하는 것이 좋은 이유!

    1. 코드 작성이 용이하다.


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
object o = Factory.GetObject();
 
//----------- as 연산자를 사용한 버전
 
MyType t = o as MyType;
 
if(t != null)
{
    // MyType 타입의 t 객체 사용.
}
else
{
    // 오류 보고.
}
 
//----------- 캐스트를 사용한 버전
 
try
{
    MyType t;
    t = (MyType)o;
    
    // MyType 타입의 t 객체 사용.
}
catch(InvalidCastException)
{
    // 오류 보고
}
cs


  캐스팅을 사용한 경우에는 예외처리 코드와 null 확인 코드 모두 필요합니다.
반면, as를 사용하면 try / catch 문을 사용할 필요가 없습니다. 때문에 성능과 가독성이 좋고, null 확인 코드만 있으면 되죠!



    2. 더 안전하고 런타임에 효율적으로 작동한다.

 

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
32
33
34
35
36
37
38
39
40
41
42
public class SecondType
{
    private MyType _value;
 
    // 중략
    // 형변환 연산자 SecondType -> MyType
    public static implicit operator MyType(SecondType t)
    {
        return t._value;
    }
}
 
SecondType o;
// obejct o = Factory.Getobject();
// o는 SecondType
 
//----- as 연산자 사용.
 
MyType t = o as MyType; // o가 MyType이 아니면 실패.
 
if(t != null
{
    // MyType 으로 형변환된 t 사용.
}
else
{
    // 실패.
}
 
//----- 캐스트 사용.
 
try
{
    MyType t1;
    t1 = (MyType) o;
    
    //MyType 형변환 된 t1 사용.
}
catch(InvalidCastException)
{
    // 형변환 오류 보고.
}
cs


  위에 캐스트와 as 연산자를 이용한 예제가 있습니다. 결과를 먼저 말하자면 두 버전 모두 실패합니다..!!
캐스트로 형변환 하는 경우, 사용자 정의 형변환 연산자가 호출되어 캐스팅에 성공할 것 같지만 그렇지 않습니다.
그 이유는 컴파일러는 단순히 컴파일 타임에 객체가 어떤 타입으로 선언됐는지만 추적하기 때문입니다.
컴파일러는 객체 o가 런타임에 어떤 타입인지 알 수 없어 객체 o가 object 타입이라고 생각하고 object 타입을 MyType으로 형변환 할 수 있는 연산자가 정의되어 있는지를 확인합니다.


  as나 is는 런타임에 객체의 타입을 확인하지만, 필요에 따라 박싱을 수행하는 것을 제외하고는 어떠한 작업도 수행하지 않습니다. 따라서, as, is 형변환은 사용자 정의 형번은 수행되지 않습니다. as, is를 사용하여 형변환을 수행하려면 변환하고자 하는 객체는 지정한 타입이거나 지정한 타입을 상속한 타입이어야 합니다. 이 외에는 모두 실패하게 됩니다.


  다시 말해, t = st as MyType; 에서 st 가 MyType 이거나 MyType을 상속한 타입이 아니라면 컴파일 오류를 발생하게 될겁니다. 이처럼 as, is 연산자가 결과의 일관성이 높고 더 안전하며 런타임에 효율적입니다.


- 사용자 정의 형변환 : https://loveme-do.tistory.com/4?category=767223




[ Digression ]


  이미 C++을 공부하거나 C#을 공부하면서 C스타일의 형변환은 지양해 와서 as, is를 사용하는게 훨씬 익숙한 편인데, 캐스트가 컴파일 타임에 객체 타입을 확인해서 추적한다거나 사용자 정의 형변환은 이번에 알게 됐네요....!!
캐스트를 완전히 쓰진 않으니 써야할 경우 이에 좀 더 안전하게 대처할 수 있지 않을까 합니다.






참 고 : Effective C# 

C# 사용자 정의 형변환 - explicit, implicit



 두 기능 모두 C#4.0부터 지원해주고 있는 기능입니다.

explicit
 명시적 사용자 정의 형변화 연산자.


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Celsius
{
    private float degress;
 
public Celsius(float degress)
    {
        this.degress = degress;
    }
    //Celsius -> Fahrenheit 명시적 형변환.
    public static explicit operator Fahrenheit(Celsius c)
    {
        return new Fahrenheit((9.0f / 5.0f) * c.degress + 32);
    }
    public float Degress
    {
        get { return this.degress; }
    }
}
 
class Fahrenheit
{
    private float degress;
 
    public Fahrenheit(float degress)
    {
        this.degress = degress;
    }
    //Fahrenheit -> Celsius 명시적 형변환.
    public static explicit operator Celsius(Fahrenheit fahr)
    {
        return new Celsius((5.0f / 9.0f) * (fahr.degress - 32));
    }
    public float Degress
    {
        get { return degress; }
    }
}
 
class TextExplicit
{
    static void Main()
    {
        Fahrenheit fahr = new Fahrenheit(100.0f);
        Celsius c = (Celsius)fahr; // Fahrenheit -> Celsius explicit conversion.
 
        Fahrenheit fahr2 = (Fahrenheit)c; //Celsius -> Fahrenheit explicit conversion.
    }
}



- implicit
 암시적 사용자 정의 형변화 연산자.


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
32
33
34
class Digit
{
    byte value;
 
    public Digit(byte value)
    {
        if (value > 9)
        {
            throw new System.ArgumentException();
        }
        this.value = value;
    }
    // byte -> Digit 암시적 형변환.
    public static implicit operator Digit(byte b)
    { 
        return new Digit(b);
    }
    // Digit -> byte 암시적 형변환.
    public static implicit operator byte(Digit d)
    {
        return d.value;
    }
}
 
class TextImplicit
{
    static void Main()
    {
        Digit d = new Digit(3);
        byte b = d; // Digit -> byte implicit conversion.
 
        Digit dig2 = b; // byte -> Digit implicit conversion.
    }
}






참 고 : https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/statements-expressions-operators/using-conversion-operators

+ Recent posts