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 ν‘œν˜„μ— μ£Όμ˜κ°€ ν•„μš”ν•΄ λ³΄μž…λ‹ˆλ‹€.

8개의 μ’‹μ•„μš”

μ•„λ§ˆ λŒ€λ¦¬μž(와 이벀트)μ—λŠ” += λ₯Ό μ“°λŠ” 게 일반적인 μŠ΅κ΄€μ΄κΈ° λ•Œλ¬Έμ—, 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개의 μ’‹μ•„μš”