Brian Kessler
Brian Kessler

Reputation: 2327

In C#, How can I create or overload an assignment operator to possibly assign two values at once?

This is probably a stupid question, but just in case....

We have a 3rd party package with weird models like:

public partial class CountingDevice
{
    public int countingDeviceNo { get; set; }
    public string countingDeviceName { get; set; }
    public string obis { get; set; }
    public int integralPart { get; set; }
    public bool integralPartFieldSpecified;
    public int fractionalPart { get; set; }
    public bool fractionalPartFieldSpecified;
    public double value { get; set; }
    public bool valueFieldSpecified;
    public bool offPeakFlag { get; set; }
    public bool offPeakFlagFieldSpecified;
    public ExpectedMeterReading expectedMeterReading { get; set; }
    // snipped for brevity
}

You'll notice that sometimes there are pairs of fields like integralPart and integralPartFieldSpecified.

Here is the problem: If I simply assign some value to integralPart but do not set integralPartFieldSpecified = true, the value of integralPart will be completely ignored causing the solution to fail.

So when mapping our own models to this madness, I need to litter the code with constructs like:

if (IntegralPart != null)
{
    countingDevice.integralPartSpecified = true;
    countingDevice.integralPart = (int)IntegralPart!;
}

Both in the interest of reducing lines of code and not stumbling over a minefield, I would like to do any one of the following:

A. Overload the = operator so it will automatically check for a property which is a boolean and has "Specified" concatenated to the current property's name. If such a property exists, it will be assigned true when the value is assigned; if not, then assignment will operate as normal. Ideally, it should be "smart" enough to assign "...Specified" to false if the value assigned is null/default/empty.

B. Create some customer operator which will do the same as A.

C. Create some method which I could invoke in a concise and preferably typesafe way to do the same.

Is this possible? If so, how?

To make it clear: I need to build quite a few wrappers. I don't want to repeat this logic for every field and worry about missing some fields which it applies to. I want a generic way of assigning both fields at once if the "Specified" field exists and being able to do assignments in exactly the same way if it does not exist.

Upvotes: 1

Views: 295

Answers (5)

Rufus L
Rufus L

Reputation: 37020

Another (expensive) solution would be to write a method that takes in an object, a property name, and the new property value. You can then use reflection to both set the property value for the specified property, as well as search for the bool field that you want to set (if it exists).

Note that you need to pass the correct type for the property. There's no compile-time checking that you're passing a double instead of a string for the value property, for example.

Below I've created an extension method on the object type to simplify calling the method in our main code (the method becomes a member of the object itself):

public static class Extensions
{
    // Requires: using System.Reflection;
    public static bool SetPropertyAndSpecified(this object obj, 
        string propertyName, object propertyValue)
    {
        // Argument validation left to user

        // Check if 'obj' has specified 'propertyName' 
        // and set 'propertyValue' if it does
        PropertyInfo prop = obj.GetType().GetProperty(propertyName,
            BindingFlags.Public | BindingFlags.Instance);

        if (prop != null && prop.CanWrite)
        {
            prop.SetValue(obj, propertyValue, null);

            // Check for related "FieldSpecified" field 
            // and set it to 'true' if it exists
            obj.GetType().GetField($"{propertyName}FieldSpecified",
                BindingFlags.Public | BindingFlags.Instance)?.SetValue(obj, true);

            return true;
        }

        return false;
    }
}

After you add this class to your project, you can do something like:

static void Main(string[] args)
{
    var counter = new CountingDevice();

    // Note that 'valueFieldSpecified' and `integralPartFieldSpecified' 
    // are set to 'false' on 'counter'

    // Call our method to set some properties
    counter.SetPropertyAndSpecified(nameof(counter.integralPart), 42);
    counter.SetPropertyAndSpecified(nameof(counter.value), 69d);

    // Now 'valueFieldSpecified' and 'integralPartFieldSpecified' 
    // are set to 'true' on 'counter'
}

Upvotes: 1

Matthew Watson
Matthew Watson

Reputation: 109567

If you implement a generic solution and add implicit conversion operators, it's quite convenient to use.

Here's a sample Optional<T> struct (I made it a readonly struct to ensure immutable mechanics):

public readonly struct Optional<T> where T : struct
{
    public Optional(T value)
    {
        _value = value;
    }

    public static implicit operator T(Optional<T> opt) => opt.Value;
    public static implicit operator Optional<T>(T opt) => new(opt);

    public T Value => _value!.Value;

    public bool Specified => _value is not null;

    public override string ToString() => _value is null ? "<NONE>" : _value.ToString()!;

    readonly T? _value;
}

You could use that to implement your CountingDevice class like so:

public partial class CountingDevice
{
    public int              countingDeviceNo   { get; set; }
    public string           countingDeviceName { get; set; }
    public string           obis               { get; set; }
    public Optional<int>    integralPart       { get; set; }
    public Optional<int>    fractionalPart     { get; set; }
    public Optional<double> value              { get; set; }
    public Optional<bool>   offPeakFlag        { get; set; }
    // snipped for brevity
}

Usage is quite natural because of the implicit conversions:

public static void Main()
{
    var dev = new CountingDevice
    {
        integralPart = 10,      // Can initialise with the underlying type.
        value        = 123.456
    };

    Console.WriteLine(dev.fractionalPart.Specified);  // False
    Console.WriteLine(dev.integralPart.Specified);    // True
    Console.WriteLine(dev.value);                     // 123.456
    Console.WriteLine(dev.value.ToString());          // 123.456
    Console.WriteLine(dev.fractionalPart.ToString()); // "<NONE>"

    dev.fractionalPart = 42;  // Can set the value using int.
    Console.WriteLine(dev.fractionalPart.Specified);  // True
    Console.WriteLine(dev.fractionalPart);            // 42

    var optCopy = dev.offPeakFlag;
    Console.WriteLine(optCopy.Specified);             // False

    dev.offPeakFlag = true;
    Console.WriteLine(dev.offPeakFlag.Specified);     // True

    Console.WriteLine(optCopy.Specified); // Still False - not affected by the original.

    Console.WriteLine(optCopy); // Throws an exception because its not specified.
}

You might also want to use optional reference types, but to do that you will need to declare a generic with the class constraint:

public readonly struct OptionalRef<T> where T : class
{
    public OptionalRef(T value)
    {
        _value = value;
    }

    public static implicit operator T(OptionalRef<T> opt) => opt.Value;
    public static implicit operator OptionalRef<T>(T opt) => new(opt);

    public T Value => _value ?? throw new InvalidOperationException("Accessing an unspecified value.");

    public bool Specified => _value is not null;

    public override string ToString() => _value is null ? "<NONE>" : _value.ToString()!;

    readonly T? _value;
}

Personally, I think that's a bit overkill. I'd just use nullable value types, int?, double? etc, but it depends on the expected usage.

Upvotes: 1

YungDeiza
YungDeiza

Reputation: 4530

You cannot overload the = operator in C#.

You can just use custom properties and set the "FieldSpecified" fields in the setters e.g.

private int _integralPart;
public int integralPart 
{ 
    get { return _integralPart; }
    set
    {
        _integralPart = value;
        integralPartFieldSpecified = true;
    }
}
public bool integralPartFieldSpecified;

Update

If you want a generic solution you can use a generic class for properties that you want to achieve the specified behaviour with e.g.

public class ValueWithSpecifiedCheck<T>
{
    private T _fieldValue;

    public T FieldValue
    {
        get
        {
            return _fieldValue;
        }
        set
        {
            _fieldValue = value;
            FieldSpecified = true;
        }
    }

    public bool FieldSpecified { get; set; }
}

public class Data
{
    public ValueWithSpecifiedCheck<int> IntegralPart { get; set; }
}

Then the class/property would be used as following:

public static void Main()
{
    var data = new Data();

    data.IntegralPart = new ValueWithSpecifiedCheck<int>();
    data.IntegralPart.FieldValue = 7;
    Console.WriteLine(data.IntegralPart.FieldSpecified);// Prints true
}

Upvotes: 1

David
David

Reputation: 218847

not stumbling over a minefield

Encapsulate the minefield.

If you don't control this 3rd party DTO then don't use it throughout your domain. Encapsulate or wrap the integration of this 3rd party tool within a black box that you control. Then throughout your domain use your models.

Within the integration component for this 3rd party system, simply map to/from your Domain Models and this 3rd party DTO. So this one extra line of code which sets a second field on the DTO only exists in that one place.

Upvotes: 3

PMF
PMF

Reputation: 17203

C# doesn't allow overloading the = operator (unlike eg C++). However, your suggestion C should work. It's a bit of a hassle, too, since you'll have to write a bunch of methods, but you could write an extension method such as


public static class Extensions
{
    public static void UpdateIntegralPart(this CountingDevice dev, double value)
    {
        dev.integralPart = value;
        dev.integralPartSpecified = true;
    }
}

Then you can call

   countingDevice.UpdateIntegralPart(1234);

Upvotes: 0

Related Questions