4

I have multiple instances of methods in classes that I need to invoke and write quickly without adding them to my main function. How would that be completed with an attribute?

e.g. I have a lot of different classes that have a method called 'invoke'. I want to add a custom attribute that I can add to this method and then call the invoke method on each one of these classes in a different method called 'invoke all'.

Something like looks like this, but functional.

public class main_class
{ 
   public void invoke_all()
   {
      // call all the invokes
   }

}

public class test1
{
   [invoke]
   public void invoke()
   {
      Console.WriteLine("test1 invoked");
   }
}
public class test2
{
   [invoke]
   public void invoke()
   { 
     Console.WriteLine("test2 invoked");
   }
}
  • Can you explain a bit more, why you need that? – SᴇM Sep 22 '17 at 07:43
  • How would you invoke a non-static method? By constructing a `test1` object first? – Willem Van Onsem Sep 22 '17 at 07:45
  • I would rather recommend to have a `delegate` in `main_class` which can have a singleton instance. Add all the methods you want to invoke all together to the delegate in each class. Invoking this delegate would in turn call all methods. – praty Sep 22 '17 at 07:46
  • It is an example, I don't know how to do it. It is just something to put along with my words. –  Sep 22 '17 at 07:47
  • @praty could I get a code sample of how that is done? –  Sep 22 '17 at 07:49
  • Do you have the instances, or do you need to discover which types has these methods as well? – Lasse V. Karlsen Sep 22 '17 at 08:05
  • In case you want to try out delegates without using Reflection, I have added my code in answer. The only difference you might notice would be performance. Reflection object read through assembly manifest. Delegates are simpler objects. – praty Sep 22 '17 at 08:08
  • Thank you for your answer. What kind of differential are we looking at? @praty –  Sep 22 '17 at 08:10
  • That would be something I would defer to you! Sorry about that. You can run code analyzer for both strategies and see the difference. – praty Sep 22 '17 at 08:14

6 Answers6

10

To call a method, you need to instantiate a class. To instantiate a class, you need to know the type.

So we need to

  1. find all classes, that contain methods marked with the Invoke attribute
  2. Then instantiate those classes
  3. Call all marked methods.

Let's first define the attribute :

public class InvokeAttribute : Attribute
{
}

You can use this attribute to mark the methods:

public class TestClass1
{
    [Invoke]
    public void Method1()
    {
        Console.WriteLine("TestClass1->Method1");
    }
    [Invoke]
    public void Method2()
    {
        Console.WriteLine("TestClass1->Method2"););
    }
}

public class TestClass2
{
    [Invoke]
    public void Method1()
    {
        Console.WriteLine("TestClass2->Method1");
    }
}

Now how to find and call these methods:

var methods = AppDomain.CurrentDomain.GetAssemblies() // Returns all currenlty loaded assemblies
        .SelectMany(x => x.GetTypes()) // returns all types defined in this assemblies
        .Where(x => x.IsClass) // only yields classes
        .SelectMany(x => x.GetMethods()) // returns all methods defined in those classes
        .Where(x => x.GetCustomAttributes(typeof(InvokeAttribute), false).FirstOrDefault() != null); // returns only methods that have the InvokeAttribute

foreach (var method in methods) // iterate through all found methods
{
    var obj = Activator.CreateInstance(method.DeclaringType); // Instantiate the class
    method.Invoke(obj, null); // invoke the method
}

The snippet above will check all loaded assemblies. The linq query

  1. selects all types and filters all classes
  2. it then reads all methods defined in those classes
  3. and checks that those methods are marked with the InvokeAttribute

This gives us a list of MethodInfos. A method info contains the DeclaringType, which is the class the method was declared in.

We can use Activator.CreateInstance to instantiate an object of this class. This will only work if the class has a public constructor without parameters.

Then we can use the MethodInfo to invoke the method on the previously created class intance. This will only work if the method doesn't have parameters.

Iqon
  • 1,920
  • 12
  • 20
1

The process is simple using reflection: find all types t of interest, get all methods m from the type t, then for each m find its custom attributes a, then if the set a contains the attribute you want, you invoke the method.

See also:

Which would look like this:

foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
{
    foreach (Type t in a.GetTypes())
    {
        // Skip types that don't have the [Invoke] attribute
        var typeContainsInvokeAttribute = t.GetCustomAttributes(typeof(InvokeAttribute)).Any();
        if (!typeContainsInvokeAttribute)
        {
            continue;
        }

        // This throws for types without a public, parameterless constructor
        var instance = Activator.CreateInstance(t);

        foreach (var methodInfo in instance.GetType().GetMethods())
        {
            var containsInvokeAttribute = methodInfo.GetCustomAttributes(typeof(InvokeAttribute)).Any();
            if (containsInvokeAttribute)
            {
                methodInfo.Invoke(instance);
            }

        }
    }
}
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • But when I have 20 different methods spread out within 20 different classes how would this be achieved? This just covers if all the invoke methods are in 1 different class, not exactly the solution I am looking for. –  Sep 22 '17 at 07:53
  • You'll simply need to lift the principle one level higher: you find all types that you wish to inspect, iterate over them, instantiate an instance of them and call this code. `Assembly.GetTypes()` does what you want. It could help if you apply the same attribute to the class as well to quickly find the types of interest. – CodeCaster Sep 22 '17 at 07:54
  • No, it doesn't, re-read my question. I explained that I need to invoke different methods throughout different classes. –  Sep 22 '17 at 07:55
  • Can't you figure it out for yourself? Each part of this answer has been asked and answered before. I've updated my answer anyway. – CodeCaster Sep 22 '17 at 08:00
  • Sure I could. Just would enjoy a piece of code with your answer. Don't feel obliged to answer. –  Sep 22 '17 at 08:02
  • No problem, happy to help. It was my misinterpretation anyway, my bad, read too quickly. – CodeCaster Sep 22 '17 at 08:03
1

You can use static event instead of Attribute

public static class Events
{
    public static event EventHandler OnInvoke;

    public static void Run()
    {
        OnInvoke?.Invoke(null, EventArgs.Empty);
    }
}

Subscribe on this event in the class constructor

public class Customer
{
    public Customer()
    {
        Events.OnInvoke += (sender, args) => Call();
    }
}

But Don't forget to Unsubscribe from this event otherwise all your objects will never be disposed

This will call your code on each Instantiated (existing) object in the application. Means if you have 2 objects of type test1 than Console.WriteLine("test1 invoked"); will be executed twice

ASpirin
  • 3,601
  • 1
  • 23
  • 32
  • You'll have to add this subscription code to each class, and instantiate each class yourself. Not an improvement from just calling `new Customer().Call()`. – CodeCaster Sep 22 '17 at 08:12
  • Yes, but you can add more than one subscription in a single place. If class has more than one methods to be called. And in the original question methods are not static, and ,as I understand, should be called not for each type, but for each created object. Means if there are 10 customers created and in some point I want to `Invoke_all` than each of those customers should execute the logic – ASpirin Sep 22 '17 at 08:16
1

You can create Interface based solution for your requirement.

I have modified your code and achieve what you want with this way.

namespace ConsoleApplication1
{
    public class main_class
    {
        static void Main(string[] args)
        {
            main_class.invoke_all();
        }

        public static void invoke_all()
        {
            // call all the invokes
            // Help : https://stackoverflow.com/questions/26733/getting-all-types-that-implement-an-interface

            foreach (Type mytype in System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
                 .Where(mytype => mytype.GetInterfaces().Contains(typeof(IInvokeAll))))
            {
                mytype.GetMethod("invoke").Invoke(Activator.CreateInstance(mytype, null), null);
            }

            //wait for user input
            Console.ReadLine();
        }

    }

    interface IInvokeAll
    {
        void invoke();
    }

    public class test1 : IInvokeAll
    {
        //[invoke]
        public void invoke()
        {
            Console.WriteLine("test1 invoked");
        }
    }
    public class test2 : IInvokeAll
    {
        //[invoke]
        public void invoke()
        {
            Console.WriteLine("test2 invoked");
        }
    }
}
Bharat
  • 5,869
  • 4
  • 38
  • 58
  • But now each class can only contain one method to invoke. – CodeCaster Sep 22 '17 at 08:06
  • @CodeCaster: yes, this is limitation, but still it's proper way to implement, I see your code, it's more optimized, in Repository pattern, I used this way for same requirement as OP has,.. – Bharat Sep 22 '17 at 08:08
  • 1
    @CodeCaster This is what BobRoss asked. _e.g. I have a lot of different classes that have a method called 'invoke'._ – Martin Backasch Sep 22 '17 at 08:08
  • 1
    @Martin yeah I definitely need more coffee. – CodeCaster Sep 22 '17 at 08:09
  • @MartinBackasch, true, and Based on only that sentence I have created Interface based solution, because OP has to invoke same method from all classes. – Bharat Sep 22 '17 at 08:14
0

It's little bit unclear to me, but I think I have a solution:

You can't do directly what you're asking, but there's workaround - create interface containing only one method: invoke (or more, as you please), in main method, create list of objects implementing your interface - or just create as a field and use it in that method. Then, in simple foreach loop you can call Invoke method on every item in the list (it will be possible, since they implement interface with that method).

Michał Turczyn
  • 32,028
  • 14
  • 47
  • 69
-2

Here is a sample code to achieve what you intend using delegates.

public class main_class
{
    private static main_class instance;

    public delegate void MethodInvoker();

    public MethodInvoker MyInvoker { get; set; }

    public static main_class Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new main_class();
            }

            return instance;
        }
    }

    private main_class() { }

    public void invoke_all()
    {
        MyInvoker();
    }

}

public class test1
{
    public test1()
    {
        main_class.Instance.MyInvoker += invoke;
    }
    public void invoke()
    {
        Console.WriteLine("test1 invoked");
    }
}
public class test2
{
    public test2()
    {
        main_class.Instance.MyInvoker += invoke;
    }
    public void invoke()
    {
        Console.WriteLine("test2 invoked");
    }
}

So now, wherever you call main_class.Instance.invoke_all(); it will invoke all the methods.

praty
  • 535
  • 2
  • 9
  • No, it doesn't. You first have to instantiate all classes on which you want to call `invoke()`. Worse, when you need do that anyway, it's easier to just call `new test1().invoke()` than to add that subscription to each class's constructor... – CodeCaster Sep 22 '17 at 08:10
  • @CodeCaster, well `invoke` is a non-static method, so there would be a need to instantiate the class anyways! – praty Sep 22 '17 at 08:13
  • Yes, but if you look at my answer, you can solve the whole problem with reflection without adding boilerplate code, and without having to manually instantiate all types. Because with your approach, if they add a new class, they'll have to copy the boilerplate (or introduce inheritance), _and_ not forget to instantiate the new class. – CodeCaster Sep 22 '17 at 08:15
  • @CodeCaster That is certainly true sir. But, it will depend on the actual world implementation for Bob. You implementation will create instance @ `instance = Activator.CreateInstance(t)` which will stay internal to that block. If Bob is looking for instances outside the scope there certainly be some issue with the implementation. – praty Sep 22 '17 at 08:19