Is there a way I can enforce a method to follow certain method signature?
Asked Answered
I

6

5

let's say I have

public delegate DataSet AutoCompleteDelegate(
      string filter, long rowOffset);

can I make the following class to enforce that method signature? (just a conjured up idea):

public class MiddleTier
{
    [Follow(AutoCompleteDelegate)]
    public DataSet Customer_AutoComplete(string filter, long rowOffset)
    {
        var c = Connect();
        // some code here
    }

    [Follow(AutoCompleteDelegate)]
    public DataSet Item_AutoComplete(string filter, long rowOffset)
    {
        var c = Connect();
        // some code here
    }



    // this should give compilation error, doesn't follow method signature
    [Follow(AutoCompleteDelegate)]
    public DataSet BranchOffice_AutoComplete(string filter, string rowOffset)
    {
        var c = Connect();
        // some code here
    }         

}

[EDIT]

Purpose: I already put attributes in my middletier's methods. I have methods like this:

public abstract class MiddleTier : MarshalByRefObject
{
    // Operation.Save is just an enum

    [Task("Invoice", Operation.Save)]
    public Invoice_Save(object pk, DataSet delta);

    [Task("Receipt", Operation.Save)]
    public Receipt_Save(object pk, DataSet delta);


    // compiler cannot flag if someone deviates from team's standard
    [Task("Receipt", Operation.Save)]
    public Receipt_Save(object pk, object[] delta); 
}

then on runtime, i'll iterate all the middletier's methods and put them to collection(attributes helps a lot here), then map them on winform's delegate functions(facilitated by interface, plugins-based system) as loaded

I'm thinking if I can make the attributes more self-describing, so the compiler can catch inconsistencies.

namespace Craft
{        
    // public delegate DataSet SaveDelegate(object pk, DataSet delta); // defined in TaskAttribute

    public abstract class MiddleTier : MarshalByRefObject
    {

        [Task("Invoice", SaveDelegate)]        
        public abstract Invoice_Save(object pk, DataSet delta);

        [Task("Receipt", SaveDelegate)]
        // it's nice if the compiler can flag an error
        public abstract Receipt_Save(object pk, object[] delta);
    }
}

I'm thinking if putting the methods on each class, it would be an overkill to always instantiate a Remoting object. And putting them on separate class, it could be harder to facilitate code reuse, let's say Invoice_Save need some info on Receipt_Open. In fact I even have a report here(crystal), which fetched data from Remoting middletier DataSet, inside the invoked method, it gets some info on other methods and merge in its own DataSet, they are all happening on middletier, no several roundtrips, all are done on server-side(middle-tier)

Islek answered 29/6, 2009 at 6:59 Comment(3)
I don't put this at an answer because I haven't practised it at all, but maybe via reflection you could (1) get all methods in the class (2) for each method check that it has the given signature and attribute.Catalano
For this concrete example, a better solution might be to refactor your code a bit and create several classes (Customer, Item, BranchOffice), each of which would implement some IAutoComplete interface and return the proper DataSet for that instance.Wilke
After reading your edit I think unit tests (as suggested by some other answers) make more sense in your scenario.Glaswegian
M
3

You could implement both the FollowAttribute you use in your example and write a Static Analysis (say, FxCop) rule that could check if any method that is tagged with that attribute has the same signature as the mentioned delegate. So it should be possible.

Manamanacle answered 29/6, 2009 at 7:6 Comment(0)
G
11

Other answers are obviously valid but nothing will guard you against forgetting to apply [Follow(AutoCompleteDelegate)] attribute on your method.

I think you would be better off making turning methods into classes that implement an interface:

public interface IAutoComplete
{
    DataSet Complete(string filter, long rowOffset);
}

public class CustomerAutoComplele : IAutoComplete
{
    public DataSet Complete(string filter, long rowOffset)
    {
        var c = Connect();
        // some code here
    }
}

and then use the factory method pattern to get your "auto completers":

public static class AutoCompleteFactory
{
    public static IAutoComplete CreateFor(string purpose)
    {
        // build up and return an IAutoComplete implementation based on purpose.
    }
}

or

public static class AutoCompleteFactory
{
    public static IAutoComplete CreateFor<T>()
    {
        // build up and return an IAutoComplete implementation based on T which
        // could be Customer, Item, BranchOffice class.
    }
}

Once you have that you could have a look at inversion of control and dependency injection to avoid hard coding the list of auto complete implementations in your factory method.

Glaswegian answered 29/6, 2009 at 7:24 Comment(3)
It might need some tweaking given that the example has two matching methods... but I'm generally in agreement. Heck even generics would do (IAutoComplete<Customer> etc) with explicit interface implementation.Tresa
Ugh. Turning to factory methods, inversion of control, and dependency injection are signs you are on the wrong path. Each one may seem innocent enough, but add them all together and you get an overly complicated mess. You should have stopped with "turning methods into classes that implement an interface". Now that is a plan that's easy to understand.Hecto
Yeah, I agree it might be a bit overarchitected (the full approach) just for a simple auto complete feature but the beauty is that it all is incremental - you can really stop at any point and still have a decent solution. I could have been more explicit about that though.Glaswegian
M
3

You could implement both the FollowAttribute you use in your example and write a Static Analysis (say, FxCop) rule that could check if any method that is tagged with that attribute has the same signature as the mentioned delegate. So it should be possible.

Manamanacle answered 29/6, 2009 at 7:6 Comment(0)
H
1

This isn't a language feature, but...

This is something you could do validation for: write unit tests that reflect over the class and fail if the signature doesn't match the attribute declaration.

PostSharp also gives you some interesting options for doing this around compilation. I don't know how exactly you would use it, but I suspect you could...

Hufnagel answered 29/6, 2009 at 7:7 Comment(0)
C
1

I would question why you'd want to do this. If you don't want the class to be changed through inheritance you can make it a sealed class. If you're worried about someone changing the class in the future you have 1 of two cases. 1) They don't understand what they're doing; nothing can prevent a bad programmer from doing bad things if they have full reign to edit program text. 2) They're extending the class functionality in a way you don't currently understand which hurts reuse.

Caresa answered 29/6, 2009 at 15:40 Comment(0)
M
0

Attributes are stored as extra meta-information during the compile - you can query them at run time but during compilation they aren't factored in.

You can't constrain a method by an attribute on it. You can restrict how the attribute is applied (i.e. only on methods, or whether more than one can be applied).

I would suggest using FxCop to warn when the attributes don't match - if you do be careful of the way events support type casts:

[Follow(AutoCompleteDelegate)]
public DataSet Customer_AutoComplete(string filter, int rowOffset)

Would be a valid delegate.

Mme answered 29/6, 2009 at 7:30 Comment(0)
K
0

No.

Sort of.

You can't get this behavior at compile time. You can, with Attributes, shove this into a simple test harness or force an immediate failure when the containing class is instantiated.

Consider the two (quickly hacked) attributes:

[AttributeUsage(AttributeTargets.Class)]
public class EnforceConforms : Attribute
{
    public EnforceConforms(Type myClass)
        : base()
    {
        MethodInfo[] info = myClass.GetMethods();

        foreach (MethodInfo method in info)
        {
            object[] objs = method.GetCustomAttributes(false);

            foreach (object o in objs)
            {
                Attribute t = (Attribute)o;

                if (t.GetType() != typeof(ConformsAttribute)) continue;

                MethodInfo mustConformTo = ((ConformsAttribute)t).ConformTo;

                ParameterInfo[] info1 = mustConformTo.GetParameters();
                ParameterInfo[] info2 = method.GetParameters();

                bool doesNotCoform = false;

                doesNotCoform |= (mustConformTo.ReturnType != method.ReturnType);
                doesNotCoform |= (info1.Length != info2.Length);

                if (!doesNotCoform)
                {
                    for (int i = 0; i < info1.Length; i++)
                    {
                        ParameterInfo p1 = info1[i];
                        ParameterInfo p2 = info2[i];

                        if (!p1.ParameterType.Equals(p2.ParameterType))
                        {
                            doesNotCoform = true;
                            break;
                        }
                    }
                }

                if (doesNotCoform)
                {
                    throw new Exception(myClass.Name + "." + method.Name + " does not conform to required delegate signature");
                }
            }
        }
    }
}

[AttributeUsage(AttributeTargets.Method)]
public class ConformsAttribute : Attribute
{
    public MethodInfo ConformTo;

    public ConformsAttribute(Type type)
        : base()
    {
        if (type.BaseType != typeof(Delegate) && type.BaseType != typeof(System.MulticastDelegate)) throw new Exception("Can only accept delegates");

        ConformTo = type.GetMethod("Invoke");
    }
}

Throw EnforceConforms(typeof(myFavoriteClass)) onto a class, and Conforms(typeof(myFavoriteDelegate)) onto the pertinent methods and then (this is the hacky part) typeof(myFavoriteClass).GetCustomAttributes(false). You could do so in a static initializer to fail "really fast" or do so in a test class (that looks for all methods in the assembly with the EnforceConforms attribute if you want to get fancy).

Generally, you probably shouldn't use this. If your design requires you check proper delegate implementations you should re-architect if possible. Plus the non-compile time bits of it make it so you're not really saving yourself much in the way of time.

Koeppel answered 29/6, 2009 at 8:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.