Conditional Ref, Ref ReadOnly, Ref Reassignment in C# 7.X

C# has gone through the major changes starting from 6.0 specifically from 7.0. I wrote a list of posts on C# new features which covers key topics that can be used in our day to day programming. You can find the previous posts list below

C# 7.X posts

Today we are going to discuss few uses of ref which provided altogether different meaning to it.

Conditional Ref: This feature was introduced in C# 7.2. Before going to this specific topic, we know that ref keyword has been in C# prior to 7.X and we used this keyword to pass the values by reference as

        static void Main(string[] args)
        {
            TestRef testRef = new TestRef() { Description = "First Object" };
            double price = 20;

            DoSomething(ref price, ref testRef);
            Console.WriteLine(price);
            Console.Write(testRef.Description);

            Console.ReadKey();
        }

        public static void DoSomething(ref double finalPrice, ref TestRef test )
        {
            finalPrice = finalPrice * 1.2;
            test = new TestRef() { Description = "Second Object" };
        }

   public class TestRef
    {
        public string Description { get; set; }
    }

Here I am passing a value type variable price and an object of a class type TestRef (we know classes are reference type). So let’s see the result before discussing it

Here we can see that  finalPrice is passed as ref parameter and when we are updating the value in the method, the updated value is available in caller function as it was passed using ref. Similarly in case of TestRef’s instance as ref parameter, a new instance is assigned to the variable then the variable in caller method also got updated with the new instance. It is because the instance testRef was pass passed using ref.

We have discussed few other usages of ref keyword which got introduced in 7.X, conditional ref are one of the interesting ones. We used ternary operator in past as

    var smallArray = new int[] { 1, 2, 3, 4, 5 };
    var largeArray = new int[] { 10, 20, 30, 40, 50 };

    int index = 7;
    int val = ((index < 5) ? smallArray[index] : largeArray[index - 5]);
    val = 0;

Here val gets assigned with value 0. If we change this value, no update takes place in the original array. Now let’s see the power of ref keyword here. We can see that based on condition first or second value is returned but now we can use ref keyword as

    ref int val = ref ((index < 5) ? ref smallArray[index] : ref largeArray[index - 5]);
    val = 0;

Here I am using the same array but here we are using the ref keyword in Consequent (smallArray), Alternative (largeArray) and put the ref at the whole ternary expression. Also, at left side added ref as ref int val. Removing any ref keyword will produce an error. If we want to store it as normal value then we may remove ref from left and outer ref from right.

Now val is pointing to the largeArray[2] and as we assigned it to 0, array also got updated.

Also as these ternary operator refers to a memory location so we may use it as LValue as

    ref ((index < 5) ? ref smallArray[index] : ref largeArray[index - 5]) = 0;

ref readonly: I discussed about ref keyword in one of my previous posts (refer here). It allows us to pass the values by reference, return the value by reference. But what about if we want to return a value which we dont want to get modified by the caller. Either we pass it by value (which creates a copy ) or we can use ref readonly. It will make sure the reference is returned (not the copy) but caller cannot modify the returned value. Let’s see an example


static void Main(string[] args)
{
    var points = new Point[] { new Point(1,2), new Point(1, 2), new Point(1, 2) };

    var point = new Point();
    point.SetPoints(points);

    ref readonly var myPoint = ref point.GetPoint(2);

    // It is a compile time error
    myPoint.Y = 10;

    Console.ReadKey();
}
	
struct Point
    {
        public int X;
        public int Y;

        public Point(int x, int y)
        {
            X = x;
            Y = y;
            points = null;
        }

        private Point[] points;

        public void SetPoints(Point[] arrPoint)
        {
            points = arrPoint;
        }

        public ref readonly Point GetPoint(int index)
        {
            if (points.Length &amp;gt; index)
                return ref points[index];
            else
                throw new KeyNotFoundException();
        }
    }

Here you can see GetPoint method in struct which returns one of the points from the array using ref keyword. Now if we want that the caller should not be able to modify it, then we need to put ref readonly in return type (not while returning). Now if you see while calling, we have to use ref on the right side and ref readonly on the left side and changing that variable would be a compile time error. As the method return ref readonly we cannot remove readonly from left side, however we can completely remove ref from both side including readonly then it will create a copy of the point.

If you remember In (about In)operator which allows the variable pass by reference but called method can’t modify that, it is opposite as Caller cannot modify the returned variable however the keywords are bit different.

As I used struct for the example which is value type and using operators like ref, In etc makes sure that we dont create a copy of that. However we know value type are easy to initialize and destroy in memory but Microsoft recommends that if the size of the struct is more than System.IntPtr.Size then we should avoid creating the copy of the struct.

Tip: Use the ref readonly for large structures and top preserve the immutability of the data structure.

Ref ReAssignment – This feature was added in C# 7.3 which allows us reassign a ref local variable to different location and obviously that should be of same type. Lets see an example

In this example I am going to use earlier Point class and just removed readonly from GetPoint method.

Here we can see a C# 7.2 feature where we get the reference of an array via ref and if we update the ref variable via another ref instance, it updates the original array as we can see in the tool tip.

Ref reassignment allows us to update ref local variables to different location which was not possible prior to C# 7.3 as

Here we can see refPoint was referencing to the last element of the array and once we assign to the first element, it started referring to the first object. This code will work only C# version is selected as 7.3.

As mentioned earlier, C# 7.X and 8.0 has lots of new features and few important ones we discussed earlier. In this post, we discussed three important features related to ref- Conditional ref, ref readonly, ref reassignment. Hope you have enjoyed the post.

Thanks
Brij

 

Advertisements