有没有办法可以强制执行方法来遵循某些方法签名?

时间:2009-06-29 06:59:45

标签: c# design-patterns attributes enforcement

让我说我有

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

我可以创建以下类来强制执行该方法签名吗? (只是一个想法):

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
    }         

}

[编辑]

目的:我已经在我的middletier方法中添加了属性。我有这样的方法:

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

然后在运行时,我将迭代所有middletier的方法并将它们放到集合中(属性在这里有很多帮助),然后将它们映射到winform的委托函数(由接口,基于插件的系统促进)作为加载

我在想是否可以使属性更具自我描述性,因此编译器可以捕获不一致性。

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

我在想如果将方法放在每个类上,总是实例化一个Remoting对象会有点过分。将它们放在单独的类中,可能更难以促进代码重用,假设Invoice_Save需要一些关于Receipt_Open的信息。事实上我甚至在这里有一个报告(水晶),它从Remoting middletier DataSet中获取数据,在调用的方法中,它获取了一些关于其他方法的信息并合并到它自己的DataSet中,它们都发生在middletier上,没有几次往返,所有这些都是在服务器端(中间层)完成的

6 个答案:

答案 0 :(得分:11)

其他答案显然是有效的,但没有任何措施可以防止您忘记在您的方法上应用[Follow(AutoCompleteDelegate)]属性。

我认为最好将转换方法转换为实现接口的类:

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

然后使用factory method pattern获取“自动完成程序”:

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

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

有了这些,您可以查看控制和依赖注入的反转,以避免在工厂方法中对自动完成实现的列表进行硬编码。

答案 1 :(得分:3)

您可以实现您在示例中使用的FollowAttributewrite a Static Analysis (say, FxCop) rule,它们可以检查是否有任何使用该属性标记的方法与提到的委托具有相同的签名。所以它应该是可能的。

答案 2 :(得分:1)

这不是语言功能,但是......

您可以对此进行验证:编写反映在类上的单元测试,如果签名与属性声明不匹配则失败。

PostSharp还为编译提供了一些有趣的选项。我不知道你会怎么用它,但我怀疑你可以......

答案 3 :(得分:1)

我会问你为什么要这样做。如果您不希望通过继承更改类,则可以将其设置为密封类。如果您担心将来有人改变课程,您会有两个案例中的一个。 1)他们不明白他们在做什么;没有什么可以防止坏程序员做坏事,如果他们完全统治编辑程序文本。 2)他们以一种你目前无法理解的方式扩展了类功能,这会伤害重用。

答案 4 :(得分:0)

属性在编译期间存储为额外的元信息 - 您可以在运行时查询它们,但在编译期间它们不会被考虑在内。

您不能通过属性约束方法。您可以限制属性的应用方式(即仅限于方法,或者是否可以应用多个属性)。

我建议使用FxCop在属性不匹配时发出警告 - 如果你注意事件支持类型转换的方式:

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

将是一名有效的代表。

答案 5 :(得分:0)

没有

排序。

您无法在编译时获得此行为。您可以使用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");
    }
}

将EnforceConforms(typeof(myFavoriteClass))抛出到一个类上,并将Conforms(typeof(myFavoriteDelegate))放到相关方法上,然后然后(这是hacky部分)typeof(myFavoriteClass).GetCustomAttributes(假)。您可以在静态初始化程序中执行此操作以“非常快”地执行此操作,或者在测试类中执行此操作(如果您想获得想象,则使用EnforceConforms属性查找程序集中的所有方法)。

通常,您可能不应该使用它。如果您的设计要求您检查正确的委托实现,则应尽可能重新设计。再加上它的非编译时间位,这样你就不会在时间上节省太多时间。