C# Language - Why does Microsoft implemented "Action" being not referenced by its owner class? Why the lifetime of action is not synchronized with its declaring class like a Delegate?

Eric Ouellet 41 Reputation points
2022-10-25T19:52:27.64+00:00

See: how-can-i-hold-a-weakreferenceaction-to-a-method-of-an-instance-until-the-inst

The lifetime of a delegate is the same as its class where it is defined.
An action is a generic way to define a delegate. It is the only way to define a generic delegate.

==> WHY an action does not have the same lifetime behavior as a delegate?
==> Having no link between an "Action" and it's class where it come from (instance method class) seems to makes it impossible to define a generic weak event class. If possible how?

.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,395 questions
.NET Runtime
.NET Runtime
.NET: Microsoft Technologies based on the .NET software framework.Runtime: An environment required to run apps that aren't compiled to machine language.
1,125 questions
{count} votes

Accepted answer
  1. Bruce (SqlWork.com) 56,686 Reputation points
    2022-10-26T00:27:51.883+00:00

    In the following code

    var weakRef = new WeakReference<Action>(inst.DoSomething);
    GC.Collect();

    You are not passing an Action defined in the class. You are using compiler sugar to allocate one to pass to the method. It will be auto deallocated after the method call. As the method does not hold a reference (definition of weak reference) the GC will free it. it is the same as:

    Action temp =  inst.DoSomething;   
    var weakRef = new WeakReference<Action>(temp);    
    temp = null;  
    GC.Collect();    
    

    This is different than if the instance defined the action as a property or field. Then the instance life would hold a reference.

    Note: Actions are not method pointers, but rather an allocated object with a method reference. It is this object that is going out of scope, not the referenced object.

    The solution is to hold a weak ref to the object and use an interface or reflection to call the method or an action method defined in the object.

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Eric Ouellet 41 Reputation points
    2022-10-27T02:08:15.85+00:00

    Thanks a lot, I think you are right.
    Can I ask this one? Here result for the following code (any idea why)?

    weakRef.Target is alive = True, expected true because inst keep a hold on SomeClass.
    weakRef.Target is alive = True, expected false, because there is no more ref on SomeClass.

    Code:

    	public static class DelegateKeeper  
    	{  
    		private static ConditionalWeakTable<object, Action> cwtAction = new ConditionalWeakTable<object, Action>();  
    		public static void KeepAlive(Action action) => cwtAction.Add(action.Target, action);  
    	}  
    
    	public class SomeClass  
    	{  
    		public void DoSomething() { }  
    	}  
    
    	public static class GcHelper  
    	{  
    		public static void Collect()  
    		{  
    			// OK surely overkill but just to make sure. I will reduce it when everyting will be understood.  
    			GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);  
    			GC.WaitForPendingFinalizers();  
    
    			GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);  
    			GC.WaitForPendingFinalizers();  
    		}  
    	}  
    
    	SomeClass inst;  
    	WeakReference<Action> weakRef;  
    
    	[TestMethod]  
    	public void TestLifeOfObject()  
    	{  
    		Proc1Scoped();  
    		GcHelper.Collect();  
    		Debug.WriteLine($"weakRef.Target is alive = {weakRef.TryGetTarget(out _)}, expected true because inst keep a hold on SomeClass.");  
    
    		Proc2Scoped();  
    		GcHelper.Collect();  
    		Debug.WriteLine($"weakRef.Target is alive = {weakRef.TryGetTarget(out _)}, expected false, because there is no more ref on SomeClass.");  
    	}  
    
    	private void Proc1Scoped()  
    	{  
    		inst = new SomeClass();  
    		var action = inst.DoSomething;  
    		weakRef = new WeakReference<Action>(action);  
    		DelegateKeeper.KeepAlive(action);  
    	}  
    
    	private void Proc2Scoped()  
    	{  
    		inst = null;  
    	}