user6443295
user6443295

Reputation:

How to call methods with method attributes?

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");
   }
}

Upvotes: 4

Views: 22659

Answers (6)

Iqon
Iqon

Reputation: 1992

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.

Upvotes: 10

ASpirin
ASpirin

Reputation: 3651

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

Upvotes: 1

Bharat
Bharat

Reputation: 6095

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");
        }
    }
}

Upvotes: 1

praty
praty

Reputation: 573

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.

Upvotes: -2

CodeCaster
CodeCaster

Reputation: 151588

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);
            }

        }
    }
}

Upvotes: 2

Michał Turczyn
Michał Turczyn

Reputation: 37337

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).

Upvotes: 0

Related Questions