Skip to content

Instantly share code, notes, and snippets.

@myroot
Last active March 6, 2024 13:32
Show Gist options
  • Save myroot/30fa5cb9dd69a3bfee607b2721b468e0 to your computer and use it in GitHub Desktop.
Save myroot/30fa5cb9dd69a3bfee607b2721b468e0 to your computer and use it in GitHub Desktop.
C# Delegate와 HashSet 그리고 WeakReference
using System;
class Program {
delegate void MyDelegate(int v);
HashSet<object> _set = new();
WeakReference<MyDelegate> _weak;
public void Test()
{
MyDelegate d1 = (MyDelegate)Fun;
if (_weak != null && _weak.TryGetTarget(out var target)) {
Console.WriteLine($"d1 == target : {target == d1}");
Console.WriteLine($"Object.ReferenceEquals {Object.ReferenceEquals(d1, target)}");
Console.WriteLine($"d1.GetHashCode() == target.GetHashCode() : {d1.GetHashCode() == target.GetHashCode()}");
}
// if (d1 is Delegate) {
// if (_set.Contains(d1)){
// _set.Remove(d1);
// }
// }
_set.Add(d1);
_weak = new WeakReference<MyDelegate>(d1);
Console.WriteLine($"--------------- set size : {_set.Count}");
GC.Collect();
GC.Collect();
GC.Collect();
}
public void CheckWeak(){
if (_weak != null && _weak.TryGetTarget(out var target)) {
Console.WriteLine($"Weak alive: {target}");
}
else {
Console.WriteLine($"Weak dead");
}
}
public void GarbageAndGC() {
for (int i = 0 ; i <10000; i++) {
byte[] b = new byte[1000];
}
GC.Collect();
GC.Collect();
}
public void Fun(int v) {}
public static void Main(string[] args) {
Console.WriteLine("Hello world");
var p = new Program();
p.Test();
p.GarbageAndGC();
p.CheckWeak();
p.Test();
p.GarbageAndGC();
p.CheckWeak();
}
}
@myroot
Copy link
Author

myroot commented Mar 6, 2024

Test()메소드는 HashSet에 InstanceMethod로부터 만들어진 Delegate를 HashSet에 저장하고 WeakReference를 통해 해당 객체를 참조하게 했는데

두번째 호출부터는 HashSet에 Delegate가 저장되어 있음에도 WeakReference로 참조하는 Delegate객체가 GC에 의해서 Collect된다.

그 이유는 Delegate 객체의 ==GetHashCode의 재정의 그리고 HashSet의 특징으로 인한 것이다.

instance method로부터 만들어진 Delegate객체는 매번 새롭게 만들어지는 객체인데,
Delegate객체는 자신이 가르키는 Method객체와 Target객체가 동일하기만 하면 Equal로 판단하고 HashCode도 동일한 값을 제공한다.

따라서 HashSet에서는 서로 다른 래퍼런스의 두 Delegate객체를 같은 객체로 판단하고, 두번째 저장 요청에서 중복으로 판단해서 저장하지 않게 되고,
그렇게 저장되지 못한 Delegate객체가 WeakReference에 매달려 있다가 GC되어 버린 것이다.

@myroot
Copy link
Author

myroot commented Mar 6, 2024

당황 포인트

  1. InstanceMethod로부터 만들어진 Delegate는 다 같은 객체로 만들어지는 줄 알았는데 아니네 (한 InstanceMethod로부터 한번 만들어지면 캐시되어서 같은 객체를 주는줄 알았다)
  2. Delegate가 만들어진 InstanceMethod가 alive하면 그것으로부터 만들어진 Delegate객체는 당연히 alive한줄 알았다.

그런데 둘다 아니네

매번 새로운 Delegate객체가 생성되고, Delegate lifetime은 원래 instance method의 life time과 관계가 없음
반대로 Delegate가 instance method의 this객체를 Target으로 저장하기에 instance method의 instance(this)가 Delegate에 의해서 lifetime이 expand될 수 있다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment