0

Let's suppose I have the following C# declaration:

struct Counters
{
    public long a;
    public long b;
    public long c;
}

Is this possible to iterate through the fields of a given instance of Counters and read their values using Interlocked.Read()? I.e.

Counters counters;
foreach (var counter in typeof(Counters).GetFields())
{
    var value = Interlocked.Read(???);
}
François Beaune
  • 4,270
  • 7
  • 41
  • 65
  • 1
    The fields do not values of their own. You need an object with values. – Arghya C Dec 17 '15 at 13:03
  • 1
    Of course. I clarified the fact that I do have an instance of `Counters`. – François Beaune Dec 17 '15 at 13:26
  • That doesn't make sense. Inevitably that struct value is going to be boxed because Reflection requires *object*. Nobody can write to it so worrying about atomicity is pointless. Very, very unlikely that this code ever does what you hope it will do. – Hans Passant Dec 17 '15 at 14:18
  • 1
    Why would nobody be able to write to the struct? Anyway, `Counters` could as well be a class. I'm not particularly attached to the value semantics in this case. I believe my question makes perfect sense. – François Beaune Dec 17 '15 at 14:27

3 Answers3

1

You can't use Interlocked.Read directly, because it requires a ref argument - and you can't use the required System.Int64& type directly.

So, back to reflection:

// You can keep this static in some helper class
var method = typeof(Interlocked).GetMethod("Read", new []{ typeof(long).MakeByRefType() });

var result = (long)method.Invoke(null, new object[] { counter.GetValue(instance) });

EDIT: This doesn't work either, I botched up my testing. You're still reading a copy that wasn't produced atomically.

This will work, though:

public delegate long AtomicReadDelegate<T>(ref T instance);

public static AtomicReadDelegate<T> AtomicRead<T>(string name)
{
  var dm = new DynamicMethod(typeof(T).Name + "``" + name + "``AtomicRead", typeof(long), 
                             new [] { typeof(T).MakeByRefType() }, true);

  var il = dm.GetILGenerator();

  il.Emit(OpCodes.Ldarg_0);
  il.Emit(OpCodes.Ldflda, typeof(T).GetField(name));
  il.Emit(OpCodes.Call, 
     typeof(Interlocked).GetMethod("Read", new [] { typeof(long).MakeByRefType() }));

  il.Emit(OpCodes.Ret);

  return (AtomicReadDelegate<T>)dm.CreateDelegate(typeof(AtomicReadDelegate<T>));
}

private readonly AtomicReadDelegate<Counters>[] _allTheReads = 
  new []
  {
    AtomicRead<Counters>("a"),
    AtomicRead<Counters>("b"),
    AtomicRead<Counters>("c")
  };

public static void SomeTest(ref Counters counters)
{
  foreach (var fieldRead in _allTheReads)
  {
    var value = fieldRead(ref counters);

    Console.WriteLine(value);
  }
}

You might want to cache the delegates you get from AtomicRead - they can be reused safely. A simple concurrent dictionary will work just fine.

Don't forget that this only supports longs; you'll need to use Interlocked.CompareExchange if you need to atomically read other types as well (apart from references and ints, of course - though depending on your code, you might need some memory barriers even in that case).

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • Thanks. In your example, is `instance` an instance of `Counters`? That doesn't seem to work since `Counters` is a `struct` but `GetValue()` requires an `object`. – François Beaune Dec 17 '15 at 13:32
  • @FrançoisBeaune `struct`s are `object`. However, the struct *will* be boxed, so you're no longer reading the same value. This also means that atomic reads are *completely* unnecessary - you're reading a copy that only exists in your scope, so noöne is going to change it anyway. Is the copy itself atomic? I doubt it. How would the runtime ensure an unbounded piece of memory is read atomically? You're probably out of luck, sorry :) The only solution is not to use structs, as far as I can tell. Are you *sure* you need to do this? Do you have the profiling to support that assertion? – Luaan Dec 17 '15 at 13:36
  • I have no problem replacing the struct by a class, if that solves the problem. I do need the fields to be read atomically from a given instance. – François Beaune Dec 17 '15 at 13:39
  • @FrançoisBeaune How do you get that instance? Value types (like `struct`) have value-semantics by default - unless you pass a `ref YourStruct` around, you've got a copy, not the original reference. – Luaan Dec 17 '15 at 13:41
  • I know. I have just one instance of `Counters` somewhere, as a private member of a class. I do not do any copy of it. – François Beaune Dec 17 '15 at 13:44
  • As a side note, `volatile` might help in some cases. Not for `long` though. – Arghya C Dec 17 '15 at 13:48
  • 1
    `volatile` does not guarantee atomicity. – François Beaune Dec 17 '15 at 13:54
  • 1
    @FrançoisBeaune Added a full sample of a method that actually works now (I've tested it with a helper method that changes the `ref` values, so I know everything is `ref`fed properly now) :) – Luaan Dec 17 '15 at 14:00
0

Values for instance fields are per object, so you need to take the value for a particular object.

Counters counter1 = new Counter() { a = 40; b = 50; c = 60; }
Type counterType = counter1.GetType();
foreach (var field in counterType.GetFields())
{
    var value = Interlocked.Read(field.GetValue(counter1));
}

In this case we get values for the fields of counter1 and not any other struct instance.

Royal Bg
  • 6,988
  • 1
  • 18
  • 24
0

If you really need an atomic long, then it's better to use atomics.net library (which is available via NuGet). If you need just read values of struct passed in your thread, then it's safe to read, since it's passed by value. But if it's passed by reference or if you work with unsafe/native code, then it's better to say what exactly you want to achieve.

Valery Petrov
  • 653
  • 7
  • 19