2

I'm trying to make a method that accepts another method, and returns the value that the inner method returns, if that inner method isn't void. I'd like to do it without differentiating between Func<> and Action<>.

Essentially, I want the wrapped method to behave the exact same way as the unwrapped method, plus some functionality provided by the wrapper. The goal is simple enough, but it's hard to wrap my head around the implementation.

public int ReturnsInteger() {
    Console.WriteLine("I return 42");
    return 42;
}

public static T WrapperMethod(Func<T> someMethod) {
    Console.WriteLine("Wrap start");
    var result = someMethod();
    Console.WriteLine("Wrap end");
    return result;
}

private static void Main() {
    var X = WrapperMethod(()=>ReturnsInt());
    Console.WriteLine("X = " + X);
    // Wrap start
    // I return 42
    // Wrap end
    // X = 42
}
public void ReturnsNothing() {
    Console.WriteLine("I return nothing");
    return;
}

public static T WrapperMethod(Action someMethod) {
    Console.WriteLine("Wrap start");
    someMethod();
    Console.WriteLine("Wrap end");
}

private static void Main() {
    WrapperMethod(()=>ReturnsNothing());
    // Wrap start
    // I return nothing
    // Wrap end
}
TheGreatB3
  • 65
  • 2
  • 7
  • You're going to need to have overloads for both `Action` and `Func`, but you could implement a `Unit` type to be returned by the wrapper for `Action`s. – Jonathon Chase Jul 10 '19 at 22:01
  • Any solution you come up with is going to be *radically* more complex, and perform *radically* worse, than what you have here. Trying to generalize this is in no way going to simplify this. Just support the two different delegates. – Servy Jul 10 '19 at 22:14
  • _"I'm trying to make a method that accepts another method, and returns the value that the inner method returns, **if that inner method isn't void**"_ -- I don't even know what that means. That is, if the inner method has a return type of `void`, then you _don't_ want your method that returns a value? C# doesn't work that way. Methods always either return a value or they don't...you can't make them conditionally return a value depending on the arguments. – Peter Duniho Jul 10 '19 at 23:00
  • You've only got half your requirements here. For them to be complete, you'll have to specify what you expect your wrapper method to do if the inner method does indeed return `void`. – John Wu Jul 10 '19 at 23:18
  • You all raise good points. I was struggling for close to an hour trying to figure out how to ask the right question, but @PeterDuniho's answer captured the gist of the problem. – TheGreatB3 Jul 11 '19 at 00:35

2 Answers2

1

As noted above, I don't entirely understand the wording of your question. However, the basic scenario seems plain: you want to generically decorate the behavior of unknown methods, handling both methods that return values and those which don't.

Whenever I run into that type of scenario, I address it by writing one wrapper method that has return type void, and then adapting via a lambda a second wrapper method. For example:

T Wrapper<T>(Func<T> func)
{
    T result = default(T);
    Wrapper(() => { result = func(); });
    return result;
}

void Wrapper(Action action)
{
    Console.WriteLine("Wrap start");
    action();
    Console.WriteLine("Wrap end");
}

This way I only have to write the wrapper logic once. There's some overhead with the Func<T> version having to create a new delegate instance and capture a local variable, but at least when I've had to do this sort of thing, the wrapper logic and wrapped logic is complex enough that the overhead of the lambda is inconsequential.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thanks for your answer. It seems odd to me that C# has to handle void methods separately from others, but this seems elegant enough. – TheGreatB3 Jul 11 '19 at 00:27
  • For better or worse, C# follows the conventions set by C and similar languages, all of which treat a method with a return type different from one without. I suppose the C# designers could have deviated from that norm, which I suspect is based on the underlying architecture (for the x86-based processors, there's a distinction between returning a value or not), but that could've caused more consternation from programmers trying to move from other languages. Indeed, I'm not aware of any mainstream, statically-typed language that doesn't carry this distinction. – Peter Duniho Jul 11 '19 at 00:31
  • @PeterDuniho Interestingly, there are a few actual uses. See https://stackoverflow.com/questions/5450748/what-is-system-void. I confess to never needing them though! – Kit Jul 11 '19 at 02:01
1

I would try to take away the fundamental language difference of a void returning method by actually giving the method something to return.

By implementing a Unit type, that is just a single value struct, then you can transform Action into Func<Unit>. This is effectively just using the adapter pattern.

Now you're just dealing with one kind of delegate in the form of Func<T>. You can then put all of the hard work into that single Wrapper method.

Try this:

void Main()
{
    var result = Wrapper(DoSomething);
}

private void DoSomething()
{
    Console.WriteLine("Test.");
}

T Wrapper<T>(Func<T> func)
{
    Console.WriteLine("Wrap start");
    var result = func();
    Console.WriteLine("Wrap end");
    return result;
}

Unit Wrapper(Action action)
{
    return Wrapper(() => { action(); return Unit.Default; });
}

/// <summary>
/// Represents a type with a single value. This type is often used to denote the successful completion of a void-returning method (C#) or a Sub procedure (Visual Basic).
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential, Size = 1)]
public struct Unit : IEquatable<Unit>
{
    public static Unit Default => default(Unit);
    public bool Equals(Unit other) => true;
    public override bool Equals(object obj) => obj is Unit;
    public override int GetHashCode() => 0;
    public override string ToString() => "()";
    public static bool operator ==(Unit first, Unit second) => true;
    public static bool operator !=(Unit first, Unit second) => false;
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • It would help if you would explain in your answer why you see this as a preferable approach vs other similar alternatives, e.g. defining `void Wrapper(Action action) => Wrapper(() => { action(); return (object)null; });`. Also, I'll note that type inference should take care your call to `Wrapper()`: `return Wrapper(() => { action(); return Unit.Default; });` – Peter Duniho Jul 11 '19 at 01:32
  • I like the improvement, but I'd still like to know how you see writing a whole new type as somehow better than just using `object` to call the `Func` version of the wrapper. – Peter Duniho Jul 11 '19 at 02:09
  • I for one think it's fascinating. Before this I had never heard of unit types, but I'm interested in trying it out. Can the same unit type be used anywhere (as in, I don't need to tweak it for my own implementations)? – TheGreatB3 Jul 11 '19 at 03:21
  • @TheGreatB3 - Yes, you can use it anywhere. It's just a value-type that can only ever be a single value. All voids just the same. – Enigmativity Jul 11 '19 at 05:59
  • @PeterDuniho - Let me think about how best to express it. it's really just using the concept from functional programming, but that's a bit of a trite answer. I'll come back with something. – Enigmativity Jul 11 '19 at 06:00