delegate chain에서 c# 9 target-typed new expressions 사용

안녕하세요.

여러분들은 target-typed new expressions를 많이 사용하시나요?

저는 위 표현을 알고나서부터 var 대신 주로 사용하고 있습니다.

최근 delegate chain 샘플 코드를 작성하다가 컴파일이 되지 않는 상황을 겪어 공유하고자 합니다.

환경은 visual studio 2022, .NET6 입니다.

컴파일 하기 전까지 어디 구문에서 에러가 발생했는지 표시가 안되고 따로 문제가 있는 부분이 없는 코드인 것 같지만 오류 CS7038 모듈 'ConsoleApp'을(를) 내보내지 못했습니다. 오류의 특정 원인을 확인할 수 없습니다. 가 발생하면서 정확한 원인을 파악할 수 없더라고요.

sharplab에서도 같은 에러가 발생합니다. :disappointed_relieved:

그나마 문제가 될 것 같은 부분이 12번 라인이라 이것저것 테스트해본 결과 += 연산일 때 target-typed new expressions가 동작을 안하는 것 같습니다.

void FooOne()
{
    Console.WriteLine("Foo One!!");
}

void FooTwo()
{
    Console.WriteLine("Foo Two!!");
}

// success
// FooDelegate fooDelegate = FooOne;
// fooDelegate += FooTwo; 

// success
// FooDelegate fooDelegate = new FooDelegate(FooOne);
// fooDelegate += new FooDelegate(FooTwo); 

// error
FooDelegate fooDelegate = new (FooOne);
fooDelegate += new (FooTwo);

fooDelegate.Invoke();

delegate void FooDelegate();

컴파일이 성공하는 두 표현을 sharplap에서 컴파일된 결과를 보면

public void Print()
{
	FooDelegate a = new FooDelegate(FooOne);
	a = (FooDelegate)Delegate.Combine(a, new FooDelegate(FooTwo));

	FooDelegate a2 = new FooDelegate(FooOne);
	a2 = (FooDelegate)Delegate.Combine(a2, new FooDelegate(FooTwo));
}

+= 연산이 Delegate.Combine으로 치환되는 모습을 확인해 볼 수 있었습니다.

하지만 target-typed new expressions를 사용하면 안되는 것으로 보아 Delegate.Combine 메서드의 매개변수가 Delegate 추상타입이라 타입을 유추할 수 없어서 그런가 싶기도 하고… 정확한 원인을 몰라서 roslyn에 github issue로 등록을 했습니다.

관련 개발자분들이 댓글을 달아주셨는데 target-typed new expressions은 대입 연산자(=)에서만 사용이 가능하다고 하지만 event는 +=가 처리가 된다고 하네요.

추가적으로 switch문을 사용하면 에러가 발생하지 않는다고 하는데… 확인해보니 정말 에러가 발생하지는 않습니다.

  • event 사용
Foo foo = new();
foo.FooEvent += FooOne;
foo.FooEvent += new(FooTwo); // 표현 가능!

foo.Print();

void FooOne()
{
    Console.WriteLine("FooOne!");
}

void FooTwo()
{
    Console.WriteLine("FooTwo!");
}

class Foo
{
    public delegate void FooDelegate();
    public event FooDelegate FooEvent;

    public void Print() => FooEvent.Invoke();
}

---> sharplab 컴파일

Foo foo = new Foo();
foo.FooEvent += new Foo.FooDelegate(FooOne);
foo.FooEvent += new Foo.FooDelegate(FooTwo);
  • switch 식 사용
void FooOne()
{
    Console.WriteLine("Foo One!!");
}

void FooTwo()
{
    Console.WriteLine("Foo Two!!");
}

string type = "two";

FooDelegate fooDelegate = new(FooOne);
fooDelegate += type switch // 표현 가능!
{
    "one" => new(FooOne),
    "two" => new(FooTwo),
    _ => throw new ArgumentException(),
};

fooDelegate.Invoke();

delegate void FooDelegate();

---> sharplab 컴파일

string text = "two";
FooDelegate fooDelegate = new FooDelegate(FooOne);
FooDelegate a = fooDelegate;
FooDelegate b;
if (!(text == "one"))
{
	if (!(text == "two"))
	{
		throw new ArgumentException();
	}
	b = new FooDelegate(FooTwo);
}
else
{
	b = new FooDelegate(FooOne);
}
fooDelegate = (FooDelegate)Delegate.Combine(a, b);
fooDelegate();

event가 이미 처리되고 있듯이 delegate도 컴파일이 가능해야 될 것 같다곤 하는데, 위 이슈가 처리가 될지는 모르겠네요.

현재 상황으로서는 delegate chain 사용 시 target-typed new expressions 표현에 주의가 필요해 보입니다.

7개의 좋아요

아마 대리자(와 이벤트)에는 += 를 쓰는 게 일반적인 습관이기 때문에, new 에 신경을 안 쓴 듯합니다.

private event Action? FooEvent;
private Action? FooDele;

public void Print()
{
    FooDele += FooOne;     FooEvent += FooOne;
    FooDele += FooTwo;     FooEvent += FooTwo;        
    FooDele?.Invoke();     FooEvent?.Invoke();
}
void FooOne() => Console.WriteLine("FooOne!");
void FooTwo() => Console.WriteLine("FooTwo!");
3개의 좋아요

대리자나 이벤트는 작성해주신 것처럼 하는 게 일반적인가 봐요. 무의식적으로 new()를 사용하다 보니 이런 상황이… :sob:

윈폼 프로젝트도 디자이너에서 이벤트 할당을 다음과 같이 하네요.

// .net framework
this.button1.Location = new System.Drawing.Point(0, 0);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
// modern .net
button1.Location = new Point(354, 109);
button1.Name = "button1";
button1.Size = new Size(75, 23);
button1.TabIndex = 0;
button1.Text = "button1";
button1.UseVisualStyleBackColor = true;
button1.Click += button1_Click;
1개의 좋아요

객체 외부에서는 이벤트에 +=, -= 연산자 밖에 못 쓰는데, 이는 event 한정자의 효과입니다.

event 한정자는 대리자의 Invoke 권한을 이벤트 소유자에게만 한정시키는 역할을 합니다.
즉, 외부 객체는 이벤트를 호출할 수 없고, 오로지 구독(+=) 과 구독취소(-=)만 할 수 있어, 발행자-구독자 패턴을 언어 차원에서 구현한 것이죠.

그런데, 이 연산자들은 대리자에게도 동일한 의미로 쓸 수 있어, 그냥 구별 않고 통일적으로 쓰는 습관이 든 것이죠.

또한, 언어 차원에서 발행자-구독자 패턴이 지원되니 모든 프레임워크에서 동일한 형태로 이벤트 처리를 하기에 위 습관이 그대로 굳어지는 것이죠.

저는 대리자나 이벤트에 new … 이런 게 붙으면 오히려 가독성이 떨어지더군요.

2개의 좋아요