【问题标题】:chaining methods in base and derived class基类和派生类中的链接方法
【发布时间】:2017-08-04 22:25:11
【问题描述】:

我有 2 个类,一个派生自另一个:

class Animal
{
    public Animal AnimalMethod() 
    {
        // do something 
        return this; 
    }   
}

class Dog : Animal
{
    public Dog DogMethod()
    {
         // do something
         return this;
    }
}

var dog = new Dog();
dog.DogMethod().AnimalMethod(); // 1 - this works   
dog.AnimalMethod().DogMethod(); // 2 - this doesn't

如何更改我的声明以便能够按照上述“2”的顺序调用方法以实现更流畅的 api?

【问题讨论】:

    标签: c#


    【解决方案1】:

    使用通用扩展方法

    Fluent/chaining 方法最适合作为通用扩展方法。泛型扩展方法知道实例变量的类型,并且可以将其作为传入的相同类型返回。

    class Animal
    {
        public string CommonProperty { get; set; }
    }
    
    class Dog : Animal
    {
        public string DogOnlyProperty { get; set; }
    }
    
    static class ExtensionMethods
    {
        static public T AnimalMethod<T>(this T o) where T : Animal
        {
            o.CommonProperty = "foo";
            return o;
        }
        static public T DogMethod<T>(this T o) where T : Dog
        {
            o.DogOnlyProperty = "bar";
            return o;
        }
    
    }
    
    class Example
    {
        static public void Test()
        {
            var dog = new Dog();
            dog.DogMethod().AnimalMethod(); // 1 - this works   
            dog.AnimalMethod().DogMethod(); // 2 - this works now
    
            Console.WriteLine("CommonProperty = {0}", dog.CommonProperty);
            Console.WriteLine("DogOnlyProperty = {0}", dog.DogOnlyProperty);
    
            var animal = new Animal();
            animal.AnimalMethod();
            //animal.DogMethod();                //Does not compile
            //animal.AnimalMethod().DogMethod(); //Does not compile
        }
    }
    

    输出:

    CommonProperty = foo

    DogOnlyProperty = 栏

    如果您需要私人/受保护的访问权限的解决方法

    扩展方法的一个缺点是它们不能访问私有或受保护的成员。您的实例方法可以。这对我来说不是问题(而且似乎整个 LINQ 库也不是问题,它们是作为扩展方法编写的)。但是,如果您需要访问权限,有一种解决方法。

    您将需要实现两次“链接”方法——一次作为实例上的接口方法和一个简单的包装器(一行代码)作为简单调用第一个方法的扩展方法。我们在实例上使用接口方法,这样编译器就不会尝试选择实例方法而不是扩展方法。

    interface IPrivateAnimal
    {
        Animal AnimalMethod();
    }
    
    interface IPrivateDog
    {
        Dog DogMethod();
    }
    
    class Animal : IPrivateAnimal
    {
        protected virtual string CommonProperty { get; set; }  //notice this is nonpublic now
    
        Animal IPrivateAnimal.AnimalMethod()  //Won't show up in intellisense, as intended
        {
            this.CommonProperty = "plugh";
            return this;
        }
    }
    
    class Dog : Animal, IPrivateDog
    {
        private string DogOnlyProperty { get; set; }  //notice this is nonpublic now
    
        Dog IPrivateDog.DogMethod()  //Won't show up in intellisense
        {
            this.DogOnlyProperty = "xyzzy";
            return this;
        }
    }
    
    static class ExtensionMethods
    {
        static public T AnimalMethod<T>(this T o) where T : class, IPrivateAnimal
        {
            return o.AnimalMethod() as T;  //Just pass control to our hidden instance method
        }
        static public T DogMethod<T>(this T o) where T : class, IPrivateDog
        {
            return o.DogMethod() as T;  //Just pass control to the instance method
        }
    }
    
    class Example
    {
        static public void Test()
        {
            var dog = new Dog();
            dog.DogMethod().AnimalMethod(); 
            dog.AnimalMethod().DogMethod(); 
    
    
            Console.WriteLine("CommonProperty = {0}", typeof(Dog).GetProperty("CommonProperty", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(dog));
            Console.WriteLine("DogOnlyProperty = {0}", typeof(Dog).GetProperty("DogOnlyProperty", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(dog));
        }
    }
    

    输出:

    CommonProperty = 插件

    DogOnlyProperty = xyzzy

    【讨论】:

    • 公平地说; LINQ 的优点是只是一组 foreach 循环,不需要私有访问
    【解决方案2】:

    您可以使用一个技巧来做到这一点;虽然我不确定我会推荐它。使Animal 泛型,您所有的流利方法都返回类型参数:

    class Animal<T> where T : Animal<T>
    {
        public T AnimalMethod() { return (T)this; }
    }
    

    现在你的 Dog 继承自 Animal 的 Dog 形式

    class Dog : Animal<Dog>
    {
        public Dog DogMethod() { return this; }
    }
    

    现在由于初始方法将返回Dog,您可以在其上调用DogMethod。这将非常难以阅读;但它会实现你的目标。

    我确实在 C# 交互中对此进行了测试,它似乎可以工作。

    显然,这被称为“奇怪重复”模式。 https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

    【讨论】:

    • 啊,“奇怪地重复出现”的模式;星期五晚上总是很好!
    • lol @MarcGravell - 或者当你在做低延迟 C++ 并且必须进行单元测试时:P
    • @MarcGravell 大声笑,显然这甚至有一个名字。我以为我只是在编造一些可怕的 hack :)
    • 这非常接近我的目标。你为什么反对它?只是可读性?
    • @ZdravkoDanev 是的;因为有人在阅读该类型约束时会遇到严重的 WTF(事实上它无论如何都是通用的)。我不认为它伤害任何东西;它只是看起来很奇怪,预期的效果并不明显。
    【解决方案3】:

    唯一的简单方法是(在Dog中):

    public new Dog AnimalMethod() 
    {
        base.AnimalMethod();
        return this; 
    }
    

    这是“方法隐藏”。

    【讨论】:

    • 这就是我现在所拥有并试图避免的。谢谢
    • 我不确定在这种情况下您应该避免使用它。如果它为你提供了你想要的所有语法和行为,那就去吧。
    猜你喜欢
    • 2018-05-16
    • 2015-06-28
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 2012-03-14
    • 2014-03-17
    • 2014-09-05
    • 1970-01-01
    相关资源
    最近更新 更多