0

我有一种用于处理图像的方法(旋转、过滤、调整大小等)。它看起来像这样:

 public Image ProcessImage(Image image, Func<ImageFactory, ImageFactory> process)
    {
        using (var imageFactory = new ImageFactory(preserveExifData: true))
        {
            using (var imageStream = new MemoryStream())
            {
                var loadResult = imageFactory.Load(image);
                var processResult = process(loadResult);
                processResult.Save(imageStream);
                return Image.FromStream(imageStream);
            }
        }
    }

由于我以这种方式使用 Func<> 我可以像这样调用我想要的编辑方法:

_imageEditor.ProcessImage(_image, im => im.Resize(size))

这意味着我可以链接方法,例如:

_imageEditor.ProcessImage(_image, im => im.Resize(size).Rotate(54).Flip(true))

我的问题是,如何根据使用输入链接这些方法?因此,如果我的用户想要同时旋转和调整大小,我可以添加 .Resize 和 .Rotate 方法(顺便说一下,它们采用不同的参数)。

当然,我可以使用一堆布尔值,但如果我有大量的编辑方法,那将变得不可能,使用起来非常非常难看。

有没有办法向这个链中添加方法,如果有,你会怎么做?

4

2 回答 2

0

将方法链接在一起的最简单方法是使用AggregateLINQ 方法。

您只需要将您的方法签名更改为Image ProcessImage(Image image, params Func<ImageFactory, ImageFactory>[] processes),然后您可以执行以下操作:

public Image ProcessImage(Image image, params Func<ImageFactory, ImageFactory>[] processes)
{
    using (var imageFactory = new ImageFactory(preserveExifData: true))
    {
        using (var imageStream = new MemoryStream())
        {
            var loadResult = imageFactory.Load(imageStream);
            var processResult = processes.Aggregate(loadResult, (r, p) => p(r));
            processResult.Save(imageStream);
            return Image.FromStream(imageStream);
        }
    }
}

现在您只需Func<ImageFactory, ImageFactory>[] processes从用户的选择中构建您的。

于 2020-04-20T03:35:27.243 回答
0

你的问题不是100%清楚。你遗漏了很多细节。但是,听起来您要么在询问如何连续调用您的方法,将结果从一次调用传递到下一次,或者如何使用在运行时创建的输入来创建此调用列表,或两者兼而有之。

如果没有Minimal, Reproducible Example,我没有任何好的方法来重现您的场景,也没有提供特定于该场景的解决方案。但是,这里展示了您可以用来实现这些目标的基本技术:

class Program
{
    private static readonly Dictionary<string, Func<int[], Func<A, A>>> _parser =
        new Dictionary<string, Func<int[], Func<A, A>>>()
    {
        { "Init", MakeInitFunc },
        { "Add", MakeAddFunc },
        { "Multiply", MakeMultiplyFunc },
        { "Negate", MakeNegateFunc },
    };

    static void Main(string[] args)
    {
        (string, int[])[] ops =
        {
            ("Init", new [] { 17 }),
            ("Add", new [] { 5 }),
            ("Multiply", new [] { 2 }),
            ("Negate", new int[0]),
        };

        Console.WriteLine(Chain(new A(), OpsToDelegates(ops)).Value);
        Console.WriteLine(Chain(new A(), OpsToDelegates(ops).Reverse()).Value);
    }

    private static IEnumerable<Func<A,A>> OpsToDelegates(IEnumerable<(string Name, int[] Args)> ops)
    {
        foreach (var op in ops)
        {
            yield return _parser[op.Name](op.Args);
        }
    }

    private static A Chain(A a, IEnumerable<Func<A, A>> ops)
    {
        foreach (Func<A, A> op in ops)
        {
            a = op(a);
        }

        return a;
    }

    private static Func<A, A> MakeInitFunc(int[] args)
    {
        return a => a.Init(args[0]);
    }

    private static Func<A, A> MakeAddFunc(int[] args)
    {
        return a => a.Add(args[0]);
    }

    private static Func<A, A> MakeMultiplyFunc(int[] args)
    {
        return a => a.Add(args[0]);
    }

    private static Func<A, A> MakeNegateFunc(int[] args)
    {
        return a => a.Negate();
    }
}

class A
{
    public int Value { get; private set; }

    public A Init(int value) { Value = value; return this; }
    public A Add(int value) { Value += value; return this; }
    public A Multiply(int value) { Value *= value; return this; }
    public A Negate() { Value *= -1; return this; }
}

上面有两个关键要素:

  1. 给定一系列Func<A, A>委托,可以通过一个简单的循环将它们链接在一起。请参阅Chain()方法以了解如何做到这一点。
  2. 给定一些用户输入,可以将其转换为Func<A, A>代表序列。实际上有很多可能的方法来解决这个特定问题。我的示例展示了一种非常简单的技术,使用字典将输入string值映射到辅助方法,这些辅助方法执行生成序列元素的实际工作。看OpsToDelegates()那个。

将这两者结合到这个简单的程序中,您可以看到如何仅从操作名称列表和应用它们的参数开始,然后将其转换为实际应用于对象的功能操作序列。

我相信您可以将这些一般想法应用到您的特定场景中。

于 2020-04-20T02:56:40.173 回答