2017-04-25 27 views
0

假设我有一个这样的类:如何使用C#属性基于字符串在类中选择方法?

public class MyMethods 
{ 

    [SpecialMethod("test")] 
    public string GetTestString(int i) 
    { 
     return string.Format("Hello world {0} times!",i); 
    } 

    [SpecialMethod("lorem")] 
    public string GetSomeLoremIpsumText(int i) 
    { 
     // ignores the 'i' variable 
     return "Lorem ipsum dolor sit amet"; 
    } 

    // ... more methods with the same signature here ... 

    public string DefaultMethod(int i) 
    { 
     return "The default method happened! The attribute wasn't found."; 
    } 

    public string ThisMethodShouldNotShowUpViaAttributes(int i) 
    { 
     return "You should not be here."; 
    } 
} 

我也有定义的属性只是这样的:

[AttributeUsage(AttributeTargets.Method)] 
public class SpecialMethodAttribute : System.Attribute 
{ 
    private string _accessor; 
    public string Accessor 
    { 
     get 
     { 
      return _accessor; 
     } 
    } 
    public SpecialMethodAttribute(string accessor) 
    { 
     _accessor = accessor; 
    } 
} 

我希望能够做的可能是这样的内容:

public class MethodAccessViaAttribute 
{ 
    private MyMethods _m; 

    public MethodAccessViaAttribute() 
    { 
     _m = new MyMethods(); 
    } 

    public string CallMethodByAccessor(string accessor, int i) 
    { 
     // this is pseudo-code, expressing what I want to be able to do. 
     Func<int, string> methodToCall = FindAMethodByAttribute(_m, accessor); 
     if (methodToCall == null) 
      return _m.DefaultMethod(i); 
     else 
      return methodToCall(i); 
    } 

    public void Test() 
    { 
     // should print "Hello world 3 times!" 
     Console.WriteLine(CallMethodByAccessor("test",3)); 

     // should print "Lorem ipsum dolor sit amet" 
     Console.WriteLine(CallMethodByAccessor("lorem",int.MaxValue)); 

     // should print "The default method happened! The attribute wasn't found." 
     Console.WriteLine(CallMethodByAccessor("I-do-not-exist",0)); 
    } 
} 

请注意,所有使用SpecialMethod属性的方法都遵循相同的方法签名。理想情况下,搜索功能将排除不匹配签名的方法,因为可以使用try/catch来测试方法是否与Func签名匹配。

我可以在正确的方向得到一个关于如何完成这一点的观点吗?

+0

这听起来像你应该重新考虑这种方法。基于“访问者”这个单词,你可能会在一个单一的方法内插入一个开关盒。除了属性之外,您可以通过几种不同的方式来完成此操作。我很好奇,这种代码的用例是什么? – JuanR

+0

我在写一个库,我希望能够导入到另一个项目中。因此对这些案例进行硬编码是不可接受的。我希望能够添加一个新的可能的方法,只需添加一个具有正确签名和相应属性的新方法即可。由于.NET自己的单元测试基本上使用了这样的方法,因此标记应该用[TestMethod]调用的方法,我认为有一种方法可以实现这一点。 – fdmillion

+0

我建议你看看接口(认为插件模式)。我会做的是创建一个接口来表示实现该方法的对象。然后,我可以加载程序集并查找实现此接口的类,实例化它们并调用方法。 – JuanR

回答

0

所以我想出了如何完成这个任务。

我创建了一个类,其中包含方法的所有类都将继承。然后我创建了一个私有方法,实际提取并创建了Func对象。

我们需要一种方法来搜索任何具有所需属性的方法。 Reflection有一个代表方法的类MethodInfo。我们可以使用一些LINQ来搜索类中的所有方法。然后,我们用一个小表达式树的乐趣生成Func对象,将实际调用该方法对这个类的具体实例:

// Locates a method on this class that has the SpecialMethod attribute of the given name 
private Func<int, string> FindMethodByAccessor(string accessor) 
{ 
    // Find all methods containing the attribute 
    var desiredMethod = this.GetType().GetMethods() 
       .Where(x => x.GetCustomAttributes(typeof(SpecialMethod), false).Length > 0) 
       .Where(y => (y.GetCustomAttributes(typeof(SpecialMethod), false).First() as SpecialMethod).Accessor == accessor) 
       .FirstOrDefault(); 

    if (desiredMethod == null) return null; 

    // This parameter is the first parameter passed into the method. In this case it is an int.  
    ParameterExpression x = Expression.Parameter(typeof(int)); 
    // This parameter refers to the instance of the class. 
    ConstantExpression instance = Expression.Constant(this); 
    // This generates a piece of code and returns it in a Func object. We effectively are simply calling the method. 
    return Expression.Lambda<Func<int, string>>(
     Expression.Call(instance, desiredMethod, new Expression[] { x }), x).Compile(); 

} 

表达式树的想法仍然是新的给我,但这个功能不会完全我想要什么 - 它会找到它正在运行的类的实例上的一个方法,其中已经使用给定的属性进行了修饰,并将其作为Func对象返回。

现在,我们可以完成这实际上调用其他方法的方法:

public string CallMethodByAccessor(string accessor, int i) 
{ 
    Func<int, string> methodToCall = FindMethodByAccessor(accessor); 

    if (methodToCall == null) 
     return DefaultMethod(i); 
    else 
     return methodToCall(i); 
} 

public string DefaultMethod(int i) { return "Unknown method requested!"; } 

我还没有添加检查,以查看与该属性的方法是否符合必要的签名,但可能很容易完成使用MethodInfo对象上的属性。最后,我们把所有这些都包装在一个类中,任何其他类都可以从中继承。然后子类实现签名后面的方法并用属性装饰它们。要按名称调用所需的方法,只需拨打CallMethodByAccessor即可。


到最后回应所有的建议,使用不同的方法,我尊重你的观点,如果用例是不同的,这绝对会是矫枉过正和不必要的。手头的特定用例涉及用户和开发人员可以配置的Web服务。 Web服务允许用户指定一个或多个“端点”。这种情况下的端点仅仅是一个字符串。我正在编写的库从Web服务接收数据,其中一个参数是所需的“端点”字符串。这个想法是,用户可以将端点写为方法,给他们任何他们想要的函数名称,然后通过属性将它们与实际的端点文本相关联。例如:

[Endpoint("user.login")] 
public string performLogin(string credentialString) 
{ 
    // ... 
} 

由于端点名称是user.login我们不能简单地将一个类命名方法使用该名称。此外,即使我们做了,我们仍然需要使用反射来挖掘类,提取正确的方法,并生成一个调用该方法的对象。因此,在这种情况下,使用属性只会使开发更容易,因为唯一的选择是使用必须严格维护的开关/大小写块。我觉得对于开发人员而言,忘记添加另一个case块或者忘记删除不再需要的case块比将它弄乱属性要容易得多。该属性将端点名称“绑定”到该方法,而不必在完全不同的代码块中跟踪方法名称和端点名称。

最后,在这种情况下,“默认”方法是绝对有用的。如果用户指示Web服务调用不存在的端点,则该应用可以返回合理的响应,例如, “端点'MyEendpoint'不存在。”这比调试API崩溃要容易得多。

0

我不喜欢给“做这个,而不是”的答案,但我不能把这里面的评论。

取而代之的是,连同所有支持代码来处理属性

public void Test() 
{ 
    Console.WriteLine(CallMethodByAccessor("test",3)); 
    Console.WriteLine(CallMethodByAccessor("lorem",int.MaxValue)); 
    Console.WriteLine(CallMethodByAccessor("I-do-not-exist",0)); 
} 

此代码完全相同的事情:

public void Test() 
{ 
    var methods = new MyMethods(); 
    Console.WriteLine(methods.GetTestString("test", 3)); 
    Console.WriteLine(methods.GetSomeLoremIpsumText("lorem", int.MaxValue)); 
} 

语言设计上的目的,这样,如果一种方法不存在,你不能称它。这有助于我们在运行时避免错误。我们不希望我们的应用程序根据我们是否正确拼写方法名来执行或忽略它。有人可能会把头发拉出来几个小时,试图弄清楚为什么应用程序不工作,然后意识到这是因为他们拼错了“lorem”。只是调用方法,“正常”的方式可以防止所有这些。如果你拼错方法名称,Visual Studio将显示错误,代码不会编译,而且它很容易找到并修复。 (Plus智能感知/自动完成甚至可以帮助我们看到可用的方法。)

此外,如果您右键单击某个方法,则Visual Studio将带您使用该方法。使用字符串属性,人们必须遵循更多的代码才能确定哪个方法实际上被调用。如果他们需要调试并逐步完成,他们需要逐步完成所有额外的工作。

有一堆类都实现相同的接口,每个都有一个方法是非常普遍的。在界面中使用更少的方法(甚至是一个)是一个很好的设计。 (见Interface Segregation Principle。)没有人会认为“丑陋”。但是,如果我们发明自己的方式,我们可能会混淆使用相同代码的其他开发人员,即使对于编写该代码的人来说,维护也很具有挑战性。

+0

我应该学习的教训是更具体地讲述用例。在这种特殊情况下,我的图书馆收到来自Web服务的请求。 Web服务允许其他开发人员指定“端点”。开发人员可以指定一个或五百个端点。我希望C#中的开发人员能够实现这些端点,并通过属性轻松“指出”哪个方法属于哪个端点。非常像Web API本身让你使用'[Route(“/ my/end/point”)]''。也就是说,我认为我有一个基于实验的解决方案,如果它有效,我会发布我自己的答案。 – fdmillion