DEV Community

loading...

Read-once variable - R1V, ROV, ... - in C#

Bruce Axtens
Programmed Canon Canola calculators in 1977. Assorted platforms and languages ever since. Assisting with HOPL.info. I am NOT looking for work -- I've got more than enough to do.
惻1 min read

So I want a variable that I can assign a value to then, later on, read it but have the read be destructive, that is, once I've retrieved the value I want to be unable to read it again.

I wrote something to do that some years ago and I'm still using it but I'm not exactly happy with it and am trying to think of a better, more generic, implementation.

    public class R1V
    {
        private int _count;
        private readonly string _value;

        public R1V(string value)
        {
            _count = 1;
            _value = value;
        }

        public string Get()
        {
            switch (_count)
            {
                case 1:
                    _count = 0;
                    return _value;
                default:
                    return null;
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

is what it is at the moment. It currently works only with strings. I want it to work with anything. Currently it doesn't forget the value it has stored, it just stops you getting access to it. That should change too.

In another object I have

LastMessage = new R1V(fullMsg);
Enter fullscreen mode Exit fullscreen mode

and then elsewhere I can execute LastMessage.Get() and know, if it's null, that there's nothing new from the object that exposes LastMessage.

Any ideas?

Discussion (15)

Collapse
peledzohar profile image
Zohar Peled • Edited

With only a bit of twitches you can get a much more flexible design:

public class ReadValueOnlyXTimesThingy<T>
{
    private T _value;

    public R1V(int maxNumberOfReads, T value)
    {
        if(maxNumberOfReads <= 0) throw new ArgumentOutOfRangeException("maxNumberOfReads must be a positive value");
        ReadsLeft = maxNumberOfReads;
        _value = value;
    }

    public int ReadsLeft {get; private set;}

    public T Value 
    {
        get
        {
            if(ReadsLeft <= 0)
            {
                _value = default(T);
            }
            ReadsLeft--;
            return _value;
        } 
    }
}

And the usage:

var selfDestructingMessage = new ReadValueOnlyXTimesThingy<string>(3, "this message will self destruct after 3 reads.");

// some code here

var theMessage = selfDestructingMessage.Value;

Notes:

  1. This allows as many reads as set in it's constructor.
  2. In case T is a value type, it's default will return a value - 0 for numbers, false for bool and so on. This might not be a desirable outcome once the value have been read the allowed number of times, since it's default value might be indistinguishable from the previous value (think of initializing this class with 1, false - you'll always get false back. One way to handle this is to restrict the T to reference types only, another is to change the property return type to a tuple of bool, T - or perhaps throw an exception - Though that would probably be the last thing I would recommend - as this kind of thing is what Eric Lippert would call a vexing exception, and as usual, he knows what he is writing about.
Collapse
bugmagnet profile image
Bruce Axtens Author

Extra love-button clicking for the use of the word "thingy".

Collapse
peledzohar profile image
Zohar Peled
Thread Thread
anras573 profile image
Anders Bo Rasmussen

It might be cleaner to return a Maybe, insted of returning null or default(T).

Thread Thread
peledzohar profile image
Zohar Peled

If you are already using this nuget, then yes. Otherwise, assuming you're only working with reference types, It introduces more complexity than a simple is object test for reference types.

Anyway, this is a good option to keep in mind.

Collapse
ankitbeniwal profile image
Ankit Beniwal šŸ™‚

Hey Bruce,

Here is my variant:

public class R1V<Type>{
        private readonly Type _value;

        R1V(Type value){
            _value = value;
        }

        Type Get(){
            try{
                return _value;
            }
            finally{
                _value = null;
            }
        }
    }
Collapse
bugmagnet profile image
Bruce Axtens Author • Edited

And then there's the property variant

using System;
using System.Diagnostics;

namespace r1v
{
    class Program
    {
        static void Main(string[] args)
        {
            var r1v = new R1V<string>("Hello World");
            Console.WriteLine(r1v.Get);
            Console.WriteLine(r1v.Get);
        }
    }

    public class R1V<Type>
    {
        private Type _value;

        public R1V(Type value) => _value = value;

        public Type Get
        {
            get
            {
                try
                {
                    return _value;
                }
                finally
                {
                    _value = default(Type);
                }
            }
        }
    }
}
Collapse
bugmagnet profile image
Bruce Axtens Author

I've created a dotnet core project and put that in it. Currently I'm getting a error in the finally clause: "A readonly field cannot be assigned to (except in a constructor or a variable initializer)".

Collapse
bugmagnet profile image
Bruce Axtens Author

Riiiight. Needed some publics.

using System;

namespace r1v
{
    class Program
    {
        static void Main(string[] args)
        {
            var r1v = new R1V<string>("Hello World");
            Console.WriteLine(r1v.Get());
            Console.WriteLine(r1v.Get());
        }
    }

    public class R1V<Type>{
        private Type _value;

        public R1V(Type value){
            _value = value;
        }

        public Type Get(){
            try{
                return _value;
            }
            finally{
                _value = default(Type);
            }
        }
    }
}
Collapse
katnel20 profile image
Katie Nelson

Looks good except Type would have to be nullable.

Collapse
upperavenue profile image
Jamie • Edited

A little late to the party, but here's my implementation using implicit conversion to and from the ReadLimit class.

class ReadLimit<T>
{
    private T _value;
    private readonly T _default;
    private int _maxReads;

    public ReadLimit(T value, int maxReads = 1, T defaultValue = default(T))
    {
        _value = value;
        _default = defaultValue;
        _maxReads = maxReads;
    }

    public int Reads { get; private set; }

    public T Value => Reads++ == _maxReads ? (_value = _default) : _value;

    public override string ToString() => Value?.ToString();

    public static implicit operator T(ReadLimit<T> limit) => limit.Value;

    public static implicit operator ReadLimit<T>(T value) => new ReadLimit<T>(value);
}
Collapse
bugmagnet profile image
Bruce Axtens Author • Edited

Thanks to everyone who contributed.

As I was fiddling with this, I thought, "hey why not just"

        public T Value
        {
            get
            {
                var _returning = _value;
                _value = default(T);
                return _returning;
            }
        }

Well, that's fine until you do this

            var r1vc = new R1V<Klasse>(new Klasse() { klasse = "Hello, Class" });
            Console.WriteLine(r1vc.Value.klasse);
            Console.WriteLine(r1vc.Value.klasse);

after having defined the class Klasse as

    public class Klasse
    {
        public string klasse { get; set; }
    }

The first WriteLine gives "Hello, Class" but the second one throw an error, namely {"Object reference not set to an instance of an object."} when evaluating var _returning = _value; so one must have atry{}finally{} in the property declaration.

LATER

In fact, even

            try{
                return _value;
            }
            finally{
                _value = null;
            }

doesn't cut it in the Klasse version.

LATER AGAIN

Actually, it does cut it. So perhaps the first one is okay too. The problem is in the second Console.WriteLine(r1vc.Value.klasse); because the second r1vc.Value returns null. So getting klasse of null is an error. Soooooo ... I need to do something like

            Console.WriteLine(r1vc.Value == null ? "" : r1vc.Value.klasse);
Collapse
ankitbeniwal profile image
Ankit Beniwal šŸ™‚

Implemented this program in java.. That's why i didn't faced those issues except the return value null conversion error.

Glad you solved it. Cheers šŸ„‚

Collapse
thebuzzsaw profile image
Kelly Brown • Edited

I use System.Threading.Channels for this. Read this if you want a super detailed introduction to channels in general. For your specific problem, I simply make a bounded channel with a capacity of 1.

var channel = Channel.CreateBounded<int>(1);

I love that the designers made the decision to have a separate ChannelReader<T> and ChannelWriter<T>. You can simply pass channel.Reader to your consumer and channel.Writer to your producer (if you desire that separation). This will give you what you want. You can call channel.Reader.TryRead. If it succeeds, it will be removed from the channel. Otherwise, it just informs you there was no value to take.

if (channel.Reader.TryRead(out var value))
{
    // Use the value.
}

Channels can be further customized by way of BoundedChannelOptions. For instance, you can specify BoundedChannelFullMode, which determines the desired outcome if you attempt to write to the channel when it's already full.

Collapse
bugmagnet profile image
Bruce Axtens Author

I am receiving that as a late Christmas present. Thank you very much.