Background
I've have a stats object that has a number of counters as properties. The counters can be updated anywhere in the application. The stats object is a singleton class (which used to have public fields of type long
and int
). The fields were updated using Interlocked.Increment
.
I changed the fields to properties and created a new class called InterlockedInt64
, to encapsulate the call to Increment
. I copy the previous stats object to a temporary filed in order to track a delta. I use the Clone
method to do that since it is a class.
The simplified code is here: https://dotnetfiddle.net/baEm8E (and below).
Problem
If the InterlockedInt64
was a struct, I wouldn't need the Clone
method. But if I change it to a struct, the Increment
doesn´t work anymore.
Can someone explain the mechanics behind? I just need to understand why using long
wrapped in a custom struct isn't updated, but a long
field is. I understand the difference between struct and class and all why using a class works.
using System;
using System.Globalization;
using System.Threading;
public class Program
{
public static void Main()
{
var test = new Stats();
test.Counter.Increment();
Console.WriteLine($"Expected 1, got {test.Counter}");
test.Counter.Increment();
Console.WriteLine($"Expected 2, got {test.Counter}");
test.Counter.Increment();
Console.WriteLine($"Expected 3, got {test.Counter}");
}
public class Stats
{
public InterlockedInt64 Counter { get; init; } = new InterlockedInt64();
}
//public class InterlockedInt64 // Works
public struct InterlockedInt64 // Does not work
{
private long _value;
public InterlockedInt64()
{
_value = 0;
}
public InterlockedInt64(long value)
{
_value = value;
}
public long Increment() => Interlocked.Increment(ref _value);
public long Decrement() => Interlocked.Decrement(ref _value);
public long Exchange(long newValue) => Interlocked.Exchange(ref _value, newValue);
public InterlockedInt64 Clone() => new(_value);
public static implicit operator InterlockedInt64(long v)
{
return new InterlockedInt64(v);
}
public static implicit operator long (InterlockedInt64 v)
{
return v._value;
}
public override string ToString() => _value.ToString(CultureInfo.CurrentCulture);
public string ToString(CultureInfo cultureInfo) => _value.ToString(cultureInfo);
}
}
edit: Removed some code and renamed Test -> Stats
CodePudding user response:
When working with struct
, you have to deal with their copies (struct
are passed by values, not by reference):
// you get a copy of test.Counter which you Increment and then throw it away
// note, that original test.Counter (its backing field) is not changed
test.Counter.Increment();
// you get a copy of test.Counter (which is unchanged) and print it out
Console.WriteLine($"Expected 1, got {test.Counter}");
If you insist on working with InterlockedInt64
being struct
, you can try dealing with references to struct
:
public class Test {
// Backing field
private InterlockedInt64 m_Counter = new InterlockedInt64();
// we want to return reference (not copy) of the m_Counter
public ref InterlockedInt64 Counter {
get {
// and we return reference to the original field (not its copy)
return ref m_Counter;
}
}
}