简体   繁体   English

有没有办法使用扩展方法在 C# 中实现以下实现?

[英]Is there way to use extension methods to achieve below implementation in C#?

I have a method which does something like shown below :我有一个方法可以执行如下所示的操作:

// check if bits 6,7,8 are zero
if ((num >> 5) != 0)
{
    //do some thing
    return false;
}

// check if bits 2 ,3,4 are zero
if ((num & 0x0E) != 0)
{
     //do something
     return false;
}

// check if bit 1 is 1
if ((num & 1) != 1)
{
    //dosomething
    return false;
 }

and now I want to add extension method like :现在我想添加扩展方法,如:

   num
   .arebitsset((6,7,8) ,(do some action and return from method if false , if true allow chaining))
   .arebitsset(2,3,4) , <same as above>)
   .......

While I know the logic for bitset checking, but I need to know how to return from the method or allow chaining based on true/false outcome.虽然我知道 bitset 检查的逻辑,但我需要知道如何从方法返回或允许基于真/假结果的链接。

Is it possible by using func?可以使用func吗? I am not sure.我不确定。

NOTE : I have 80 such conditions to be tested below , so is it good to write 80 if conditions , certainly not , so i need some compact form注意:我有 80 个这样的条件要在下面进行测试,所以写 80 if 条件是否好,当然不是,所以我需要一些紧凑的形式

You could write extension methods like this:你可以写这样的扩展方法:

public static class BitSetExtensions
{
    private static bool AreBitsSet(int i, int[] bits)
    {
        // you already have this
    }

    public static (int value, bool valid) CallActionIfBitsSet(this int value, int[] bits, Action action)
    {
        return CallActionIfBitsSet((value, true), bits, action);
    }

    public static (int value, bool valid) CallActionIfBitsSet(this (int value, bool valid) data, int[] bits, Action action)
    {
        if (data.valid)
        {
            data.valid = AreBitsSet(data.value, bits);

            if (data.valid) action();
        }

        return data;
    }
}

Then you can chain them like this:然后你可以像这样链接它们:

int num = 5;

num.CallActionIfBitsSet(new[] {1, 3, 5}, () =>
    {
        /* action */
    })
    .CallActionIfBitsSet(new[] {2, 3, 4}, () =>
    {
        /* other action */
    })
    .CallActionIfBitsSet(new[] {2, 3, 6}, () =>
    {
        /* other action */
    });

I am not a big fan personally because I don't think your interface gets easier this way compared to traditional if , but it would work.我个人不是一个大粉丝,因为与传统的if相比,我认为您的界面不会以这种方式变得更容易,但它会起作用。

There is one more way to achieve something that should be ok for you.还有另一种方法可以实现对您来说应该没问题的事情。

You can define structure as in code below.您可以在下面的代码中定义结构。 Using pattern matching and basing on monads, specifically on flatMap function with signature that you can see below, it is possible to create a chain of functions that would stop executing completelty transparent to the caller, based only on what happens inside those functions.使用模式匹配和基于 monad,特别是带有签名的flatMap函数,您可以在下面看到,可以创建一个函数链,这些函数将停止执行对调用者完全透明,仅基于这些函数内部发生的事情。

You can read more about it if you search for Rust's Option or Haskell's Maybe .如果您搜索 Rust's Option或 Haskell's Maybe您可以阅读更多相关信息。 Those types ( sum types ) that those languages use are not however native to C#, they need some effort to implement.然而,这些语言使用的那些类型(和类型)并不是 C# 原生的,它们需要一些努力来实现。

For example in code below only例如仅在下面的代码中

1 2 2 I got None! 1 2 2 我没有!

Will be displayed to console as third call to FlatMap breaks the chain returning None .当第三次调用FlatMap中断返回None的链时,将显示到控制台。

This can be used to your particular problem, however it can be also used to a lot of other problems.这可以用于您的特定问题,但是它也可以用于许多其他问题。

By inspecting type of final value you can learn if the chain of calls was succesfull.通过检查最终值的类型,您可以了解调用链是否成功。

The pro of this approach is that all ifs are completely transparent to the caller.这种方法的ifs是所有的ifs对调用者都是完全透明的。

class Program
{
    static void Main()
    {
        var a = new Option<int>.Some(1);
        var result = a.FlatMap<int>(i => { Console.WriteLine(i); return new Option<int>.Some(i + 1); })
         .FlatMap<int>(i => { Console.WriteLine(i); return new Option<int>.Some(i); })
         .FlatMap<int>(i => { Console.WriteLine(i); return new Option<int>.None(); })
         .FlatMap<int>(i => { Console.WriteLine(i); return new Option<int>.Some(i); });

        switch (result)
        {
            case Option<int>.Some some:
                Console.WriteLine("I got Some!");
                break;
            case Option<int>.None none:
                Console.WriteLine("I got None!");
                break;
            default:
                throw new InvalidOperationException();
        }

    }
}

public abstract class Option<T>
{
    public class Some : Option<T>
    {
        public Some(T data)
        {
            Data = data;
        }

        public T Data;
    }

    public class None : Option<T> { }

    public Option<T> FlatMap<T>(Func<T, Option<T>> action)
    {
        switch (this)
        {
            case Option<T>.Some some:
                return action(some.Data);
            case Option<T>.None none:
                return none;
            default:
                throw new InvalidOperationException();
        }
    }
}

You cannot return from the middle of a method chain.您不能从方法链的中间返回。 You could invent a way to mimick such a behaviour but it would be not so nice, as one reading the method call alone would not suspect such a behaviour.您可以发明一种方法来模仿这种行为,但它不会那么好,因为单独阅读方法调用的人不会怀疑这种行为。

Since you also asked for this in comment, an alternative method if you want to avoid the 80 "if"s not using extension methods would be to have a list of pair of test and action to perform if the test is successful, test each in a foreach loop.由于您也在评论中提出了这一要求,如果您想避免 80 个“if”不使用扩展方法,那么另一种方法是在测试成功时列出要执行的测试和操作对列表,测试每个一个 foreach 循环。 I don't think you would gain any readability, but there would be only one "if" in your code.我认为您不会获得任何可读性,但是您的代码中只会有一个“如果”。

Using a simple class like this one (alternatively, ValueTupple could be used to get rid of this class):使用这样一个简单的类(或者,可以使用 ValueTupple 来摆脱这个类):

public class Test
{
    public Func<int, bool> Condition { get; set; }
    public Func<int, bool> Operation { get; set; }
}

You could do:你可以这样做:

    var Tests = new List<Test>
    {
        new Test
        {
            Condition = num => (num >> 5) != 0,
            Operation = num => { // Do stuff}
        },
        new Test
        {
            Condition = num => (num & 0x0E) != 0,
            Operation = num => { // Do other stuff}
        }
        // Add all your 80+ tests here
    };

// Then use a single if:
foreach (var test in Tests)
{
    if(test.Condition(Number))
    {
        test.Operation(Number);
        // Whatever the test that succeeded was, do here what you need to perform before returning, if needed.
        return false;
    }
}

Without throwing big words like monads around, you can construct an object representing the entire execution plan and then short-circuit it from the inside.不用像 monad 这样的大词,你可以构造一个代表整个执行计划的对象,然后从内部短路它。 It'd be very akin to making your own LINQ method, only instead of enumerating the source and then yielding the results, we need to be able to run the plan or extend it (continue) with another action.这与创建自己的 LINQ 方法非常相似,只是我们不需要枚举源然后产生结果,我们需要能够运行计划或使用另一个操作扩展它(继续)。 Our IExecutable<T> will represent a chain of actions dependent on a parameter of type T that can be executed, returning a status that allows other methods in the chain to modify the control flow as needed.我们的IExecutable<T>将表示依赖于可以执行的T类型参数的动作链,返回一个状态,允许链中的其他方法根据需要修改控制流。 We'll need a unary parameter and parameterless version:我们需要一元参数和无参数版本:

public enum ExecutionResult
{
    RunToCompletion,
    Aborted
}

public interface IExecutable
{
    ExecutionResult Execute();
}

public interface IExecutable<T>
{
    ExecutionResult Execute(T argument);
}

We'll make two (technically three) implementations.我们将进行两个(技术上为三个)实现。 First, a SimpleExecutable and SimpleExecutable<T> , that won't do any fancy stuff, just wrap an action and run it.首先,一个SimpleExecutableSimpleExecutable<T> ,它们不会做任何花哨的事情,只需包装一个动作并运行它。

internal class SimpleExecutable : IExecutable
{
    private readonly Func<ExecutionResult> _action;

    public SimpleExecutable(Func<ExecutionResult> action) => _action = action;

    public ExecutionResult Execute() => _action();
}

internal class SimpleExecutable<T> : IExecutable<T>
{
    private readonly Func<T, ExecutionResult> _action;

    public SimpleExecutable(Func<T, ExecutionResult> action) => _action = action;

    public ExecutionResult Execute(T argument) => _action(argument);
}

Nothing interesting here, now the one you actually want, a ShortCircuitingConditionalExecutable<T> :这里没什么有趣的,现在是你真正想要的,一个ShortCircuitingConditionalExecutable<T>

internal class ShortCircuitingConditionalExecutable<T> : IExecutable<T>
{
    private readonly Func<T, ExecutionResult> _action;
    private readonly Func<T, bool> _predicate;

    public ShortCircuitingConditionalExecutable(
        Func<T, ExecutionResult> action,
        Func<T, bool> predicate) =>
        (_action, _predicate) = (action, predicate);

    public ExecutionResult Execute(T argument)
    {
        if (!_predicate(argument))
        {
            return ExecutionResult.Aborted;
        }

        _action(argument);
        return ExecutionResult.RunToCompletion;
    }
}

Execute checks the predicate, runs the method if it's true and returns a result signalling whether we should short-circuit the operation. Execute检查谓词,如果为真则运行该方法并返回一个结果,表明我们是否应该短路该操作。

To make this more handy to use, let's make a few helper extensions:为了让这个更方便使用,让我们做一些辅助扩展:

public static class Executable
{
    public static IExecutable<T> StartWithIf<T>(
        Action<T> action,
        Func<T, bool> predicate) =>
        new ShortCircuitingConditionalExecutable<T>(ReturnCompletion(action), predicate);

    public static IExecutable ContinueWith(this IExecutable source, IExecutable executable) =>
        new SimpleExecutable(ChainIfCompleted(source.Execute, executable.Execute));

    public static IExecutable<T> ContinueWith<T>(this IExecutable<T> source, IExecutable<T> executable) =>
        new SimpleExecutable<T>(ChainIfCompleted<T>(source.Execute, executable.Execute));

    public static IExecutable<T> ContinueWithIf<T>(
        this IExecutable<T> source,
        Action<T> action,
        Func<T, bool> predicate) =>
        source.ContinueWith(new ShortCircuitingConditionalExecutable<T>(ReturnCompletion(action), predicate));

    public static IExecutable BindArgument<T>(this IExecutable<T> source, T argument) =>
        new SimpleExecutable(() => source.Execute(argument));

    private static Func<ExecutionResult> ChainIfCompleted(
        Func<ExecutionResult> action1,
        Func<ExecutionResult> action2) =>
        () =>
        {
            var result = action1();
            return result != ExecutionResult.RunToCompletion ? result : action2();
        };

    private static Func<T, ExecutionResult> ChainIfCompleted<T>(
        Func<T, ExecutionResult> action1,
        Func<T, ExecutionResult> action2) =>
        t =>
        {
            var result = action1(t);
            return result != ExecutionResult.RunToCompletion ? result : action2(t);
        };

    private static Func<T, ExecutionResult> ReturnCompletion<T>(Action<T> action) =>
        t =>
        {
            action(t);
            return ExecutionResult.RunToCompletion;
        };
}

I've omitted a few obvious extensions we could make, but that's rather a lot of code, so maybe a motivating example of this thing in action:我已经省略了一些我们可以做的明显的扩展,但那是相当多的代码,所以也许是这个东西在行动的一个激励例子:

class Program
{
    private static Func<int, bool> AreBitsSet(params int[] bits) => 
       n => bits.All(b => (n & (1 << b)) != 0);

    private static void Print(int n) => Console.WriteLine(n);

    static void Main(string[] args)
    {
        var num1 = (1 << 2) | (1 << 4) | (1 << 5);
        var num2 = num1 | (1 << 7);

        var executable = Executable.StartWithIf(Print, AreBitsSet(2))
            .ContinueWithIf(Print, AreBitsSet(2, 4, 5))
            .ContinueWithIf(Print, AreBitsSet(7));

        var executableNum1 = executable.BindArgument(num1);
        var executableNum2 = executable.BindArgument(num2);

        var program = executableNum1.ContinueWith(executableNum2);

        executable.Execute(num1);
        executable.Execute(num2);
        program.Execute();
    }
}

The first execution prints 52 twice ( num1 ).第一次执行两次打印52 ( num1 )。 The second execution prints 180 thrice ( num2 ).第二次执行打印180三次 ( num2 )。 The execution of program prints only 52 twice and short-circuits the rest of the execution. program的执行只打印52两次,并且短路了其余的执行。

This is not a fully production-ready module, as I've ignored argument validation and there are probably possible optimisations that would avoid some allocations when constructing the execution flow (we're making a lot of delegates).这不是一个完全可用于生产的模块,因为我忽略了参数验证,并且可能有一些优化可以避免在构建执行流程时进行一些分配(我们正在制作大量委托)。 Also, it would be really nice to make this thing more fluent by enabling execution.ContinueWith(action).If(predicate) syntax.此外,通过启用execution.ContinueWith(action).If(predicate)语法使这件事更加流畅会非常好。 It's also terribly overcomplicated, since your use case requires only a very specific execution chain, but what the heck, it was fun writing it up.它也非常复杂,因为您的用例只需要一个非常具体的执行链,但到底是什么,写起来很有趣。

Aside: To anyone interested, this is not a monad, but very close so.旁白:对任何感兴趣的人来说,这不是一个 monad,但非常接近。 We would have to replace Execute with something that returns the constructed Action<T> delegate and then write up the appropriate bind function.我们必须用返回构造的Action<T>委托的东西替换Execute ,然后编写适当的bind函数。 Then we'd get a monad over Action s of any T , and we could still make Execute into an extension method that gets the delegate and immediately calls it.然后我们会在任何T Action获得一个 monad,我们仍然可以将Execute变成一个扩展方法,该方法获取委托并立即调用它。 The BindArgument is a special case of a bind : t => bind(e, f => () => f(t)) . BindArgumentbind一个特例: t => bind(e, f => () => f(t))

Checking the conditions with an if statement is the most efficient and the easiest to debug.使用if语句检查条件是最有效且最容易调试的。 In case of an exception you'll know where to look for the bug.如果出现异常,您将知道在哪里查找错误。 My only suggestion is to get rid of the recurrent return false;我唯一的建议是摆脱经常性return false; statements, by using a single if with multiple else if :语句,通过使用单个if和多个else if

public static bool Execute(int num)
{
    if ((num >> 5) != 0) // check if bits 6, 7, 8 are zero
    {
        // do something
    }
    else if ((num & 0x0E) != 0) // check if bits 2, 3, 4 are zero
    {
        // do something
    }
    else if ((num & 1) != 1) // check if bit 1 is 1
    {
        // do something
    }
    else
    {
        return false; // nothing has been done
    }
    return true; // something has been done
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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