Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a method on every instance of a type in c#

I know that you can call an instance method that executes for each object. I also know that you can have a static method on the type that is callable from the type.

But how would one call a method that acts on every instance of a particular type (say, to set a member variable to zero, for example)?

like image 905
Robert Harvey Avatar asked Feb 22 '23 01:02

Robert Harvey


2 Answers

C# doesn't provide a direct mechanism to track all reachable objects and there's almost never a good reason to want such automatic tracking functionality (rather than, say, an explicit pool that you manage yourself).

But to answer your requirement directly, you'll need to:

  1. Manually track all the reachable instances of the type (perhaps with a set of weak-references to prevent the tracker itself from extending the objects' lifetimes).
  2. Provide a mechanism to (say) perform a side-effect on each member of this set.
  3. Get rid of the associated weak-reference from the set when an object is destroyed. This is necessary to prevent a weak-reference leak.

All of this will have to be done in a thread-safe manner.

public class Foo
{
    private static readonly HashSet<WeakReference> _trackedFoos = new HashSet<WeakReference>();
    private static readonly object _foosLocker = new object();

    private readonly WeakReference _weakReferenceToThis;

    public static void DoForAllFoos(Action<Foo> action)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        lock (_foosLocker)
        {
            foreach (var foo in _trackedFoos.Select(w => w.Target).OfType<Foo>())
                action(foo);
        }
    }

    public Foo()
    {
       _weakReferenceToThis = new WeakReference(this);

        lock (_foosLocker)
        {
            _trackedFoos.Add(_weakReferenceToThis);
        }
    }

    ~Foo()
    {
        lock (_foosLocker)
        {
            _trackedFoos.Remove(_weakReferenceToThis);
        }
    }
}

Are you sure you need all of this though? This is all really strange and non-deterministic (will be heavily impacted by when garbage-collection occurs).

like image 86
Ani Avatar answered Mar 07 '23 10:03

Ani


If you can modify the type you are talking about, you can create a static List in that class where you keep a reference of every object created.

When you'll run your method, you just have to loop over all that list and run what you want.

If you can't modify that type so you can create this List, you can't do it without some hacks, I can suggest you using the Factory pattern, so you can still keep a List of objects of that type, provided that you use that factory.

Note: If you are not going to access the list with [ ] operator (through an index I mean) but only through a foreach, I suggest you to use a LinkedList which will be far more efficient in this case (lot of add/remove operations, no random access and you'll avoid array resize like list does).

Example:

using System.Linq.Expressions;

class MyClassFactory
{
    LinkedList<MyClass> m_Instances = new LinkedList<MyClass>();

    MyClass Create()
    {
        m_Instances.AddLast(new MyClass());
        return m_Instances.Last.Value;
    }

    void Destroy(MyClass obj)
    {
        m_Instances.Remove(obj);
    }

    void Execute(Expression<Action<MyClass, object>> expr, object param)
    {
        var lambda = expr.Compile();
        foreach (var obj in m_Instances)
            lambda(obj, param);
    }
}

You can then easily instanciate and use always that factory to instanciate your class. It's not a perfect solution, but it's a clean approach to at least work with your problem.

You can use lambda expressions to execute a method on all those instances, there are also other approaches but that is beautiful:

MyFactory f = new MyFactory();
for (int i = 0; i < 5; i++)
    f.Create();
f.Execute((obj, param) =>
{
    //Do something
}, null);

*Edit: * (about Ben Voigt comment)

Weak reference approach, to keep using garbage collection:

using System.Linq.Expressions;

class MyClassFactory
{
    HashSet<WeakReference> m_Instances = new HashSet<WeakReference>();

    MyClass Create()
    {
        var obj = new MyClass();
        m_Instances.Add(new WeakReference(obj));
        return obj;
    }

    void Execute(Expression<Action<MyClass, object>> expr, object param)
    {
        var lambda = expr.Compile();
        // Hope syntax is ok on this line
        m_Instances.RemoveWhere(new Predicate<WeakReference>(obj => !obj.IsAlive));

        foreach (var obj in m_Instances)
            lambda(obj, param);
    }
}

Check RemoveWhere to see it's usage

like image 45
Francesco Belladonna Avatar answered Mar 07 '23 11:03

Francesco Belladonna