4

I have the following extension method for monitoring a variable and awaiting until it has a given expected value:

        public static async Task AwaitForValue<T>(this Func<T> valueGetter, T expectedValue, int? millisecondsToAwaitBetweenChecks = null, int? maxMillisecondsToAwait = null) 
            where T : struct
        {
            var timeToAwait = millisecondsToAwaitBetweenChecks ?? 20;
            if (maxMillisecondsToAwait.HasValue)
            {
                var stopWatch = new Stopwatch();
                stopWatch.Start();
                while (!valueGetter().Equals(expectedValue) || stopWatch.ElapsedMilliseconds >= maxMillisecondsToAwait)
                {
                    await Task.Delay(timeToAwait);
                }
            }
            else
            {
                while (!valueGetter().Equals(expectedValue))
                {
                    await Task.Delay(timeToAwait);
                }
            }    
         }

It is working just fine:

class Foo
        {
            private bool _flag;

            public async void DoWork()
            {
                Task.Run(() =>
                {
                    Thread.Sleep(5000);
                    _flag = true;
                });
                await new Func<bool>(() => _flag).AwaitForValue(true);
                Console.WriteLine(_flag);
            }
         }

I would like to define a ref extension method that gives me a delegate that returns me the current value of the ref variable, something like:

public delegate ref T RefFunc<T>();

This way my previous extension method could extend RefFunc<T> instead of Func<T> and be consumed, hypothetically, like the following:

_flag.ToRefFunc().AwaitForValue(true);

The problem is that I can not find a way to properly define ToRefFunc<T>(this ref T value); since ref is not allowed in lambdas. So, is there a way to define the following method?

public static RefFunc<T> ToRefFunc<T>(this ref T value) where T : struct
{
     //todo some day
}

Ideally it will look like this:

public static RefFunc<T> ToRefFunc<T>(this ref T value) where T : struct => new RefFunc<T>(() => value);

Any help would be appreciated

EDIT

The problem is not whether ref extension methods are allowed, from the "duplicate" question see this

EDIT 2 The anonymous delegate syntax does not work either:

public static RefFunc<T> ToRefFunc<T>(this ref T value) where T: struct
{
    return delegate { return value; }; 
}

Here I got: Cannot use ref, out, or in parameter 'value' inside an anonymous method, lambda expression, query expression, or local function

taquion
  • 2,667
  • 2
  • 18
  • 29
  • 1
    @Joelius in C# 7.2 you can actually define ref extension methods as long as they are extending structs – taquion Aug 16 '19 at 17:37
  • 1
    I'll take it back then :) Did not know that. The [docs page](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) doesn't cover that or am I blind? Didn't find it [here](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref) either. Is there a docs-page for this? – Joelius Aug 16 '19 at 17:50
  • @Joelius that is actually weird, when I was about to use it I found [this](https://github.com/dotnet/csharplang/issues/186), however in the official docs for what is new in c# 7.2 there is nothing, see [here](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7-2) – taquion Aug 16 '19 at 17:58
  • I have opened an issue in the docs page: [here](https://github.com/dotnet/docs/issues/13877) – taquion Aug 16 '19 at 18:14
  • I think it would make sense to add a bit more information to the issue and also include a link to this question probably. – Joelius Aug 16 '19 at 18:47
  • what about you get back to work and use while(_flag) await task.delay(100); ;) – impoetk Aug 16 '19 at 19:20
  • 1
    Not *exactly* what you're asking for, but you can create some ObservableBase class with this AwaitForValue() function. This helps with any scope issues you might encounter trying to access an arbitrarily scoped value type. And you can do something like AwaitForValue(nameof(_flag), true). – chill94 Aug 16 '19 at 19:29
  • The root problem is similiar (I think) to [this one](https://stackoverflow.com/questions/2256048/store-a-reference-to-a-value-type). This becomes fairly easy though if you can guarantee scope/lifetime. – chill94 Aug 16 '19 at 19:32
  • @chill94 thanks for your suggestion, I know I can define a sort of `Awaiter` class, however, I would prefer to do it with the extension method approach, because is more semantic and performant that having to allocate and box an struct into an awaiter wrapper class. – taquion Aug 16 '19 at 19:37
  • @impoetk thanks for your suggestion, but I am looking for a more elegant and generic solution =) – taquion Aug 16 '19 at 19:38
  • I'm struggling with the idea of awaiting a value type with arbitrary scope. If these values you're awaiting are always instance members you could change the signature of your function to: AwaitForValue(this object parent, string propertyName, T value). Then, usage would be exactly what I have listed above (in hindsight, my first suggestion seems significantly inferior to just defining the extension method on "object" ). In this case, you don't need any new classes. – chill94 Aug 16 '19 at 20:02
  • When lambdas are too limiting, try the anonymous delegate syntax. – Ben Voigt Aug 16 '19 at 20:26
  • @BenVoigt, thanks for your suggestion, unfortunately that does not work, see my edits – taquion Aug 16 '19 at 21:12
  • 1
    @taquion: Oh sorry, I misunderstood and thought you were trying to define an anonymous function with a ref return type. You're trying to close on a ref parameter in the calling scope. That just isn't allowed. – Ben Voigt Aug 16 '19 at 21:53

1 Answers1

1

A ref parameter is something you can only use within your function, not something you can "save for later". That's because the ref parameter cannot keep its referent variable alive.

Consider using a reference type (C# parlance: class) to hold your data. Then you can have any number of handles to it, and they'll participate in garbage collection (object lifetime, memory compaction, etc). Unfortunately, this requires changing all users of the variable -- it's not something you can retrofit onto data in an existing object model.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720