简体   繁体   中英

C# How to implement polymorphism in a functional style?

I have some OOP code that I am wanting to translate into a functional style, using only immutable types and with only freestanding ( static ) side-effect-free functions.

Here is a much simplified version of one scenario:

abstract class Thing { }

class Sphere : Thing {
    public readonly double Radius;
     public Sphere(double r) { Radius = r; }
}

class Cube : Thing {
     public readonly double Side;
     public Cube(double s) { Side = s; }
}

Previously, Thing defined an abstract method, void Grow(double ratio) . I have converted the two concrete implementations into freestanding static functions:

Sphere Grow(Sphere s, double ratio) => new Sphere(s.Radius*ratio);
Cube Grow(Cube c, double ratio) => new Cube(c.Side * ratio);

In the OOP version, I could use polymorphism to enumerate over a collection of type Thing and cause each to grow eg:

things.foreach(x => x.Grow(r));

But how can I do this in the functional approach? I know I can write eg this:

Thing Grow(Thing t, double ratio) => t switch
{
     Sphere s => Grow(s, ratio),
     Cube c => Grow(c, ratio),
     _ => throw new Exception()
};

and

things.Select(t => Grow(t, r))

but I don't like the idea of having to extend the switch expression for each new implementation of Thing that I add.

I can see a way (I think) to do it using reflection, but am not keen on that for other reasons.

Is there a simpler way to achieve the equivalent of OOP polymorphism using functional patterns? (in C#, that is - I know it can be done in eg Haskell).

Try to define and use generic interface for Grow method like this.

interface IGrowable<T> where T: Thing { 
    T Grow(T t, double ratio);
}

class Sphere : Thing, IGrowable<Sphere> {
    public readonly double Radius;
    public Sphere(double r) { Radius = r; }
    public Sphere Grow(Sphere s, double ratio) => new Sphere(s.Radius*ratio);

}

class Cube : Thing, IGrowable<Cube> {
     public readonly double Side;
     public Cube(double s) { Side = s; }
     public Cube Grow(Cube c, double ratio) => new Cube(c.Side * ratio);
}
IEnumerable<IGrowable<T>> GrowOwn<T>(IEnumerable<IGrowable<T>> grows, double ratio){
    return grows.Select(x => x.Grow(x,ratio));
}

EDIT

If you want to use reflection, define these class

public abstract class Thing {}

public class Sphere : Thing {
    public readonly double Radius;
    public Sphere(double r) { Radius = r; }
    public Sphere(Sphere s, double ratio){ Radius = s.Radius * ratio; }
}

public class Cube : Thing {
     public readonly double Side;
     public Cube(double s) { Side = s; }
     public Cube(Cube c, double ratio){ Side = c.Side * ratio; }
}

and this.

T Grow<T>(T thing, double ratio) where T: Thing => (T)Activator.CreateInstance(typeof(T) , thing,  ratio);

You can get Thing object from this function.
But there is a risk of MissingMethodException . And reflection is not fast.
( see .NET's design goal , and using Expression Tree might help processing speed)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM