Mark Roworth
Mark Roworth

Reputation: 566

In C#, can I declare a property so that it can return just one of two types

I have a property (well, lots of them), that I currently have declared as object, but that I'd like to be able to restrict down to just two types. For example, I want a property that will either take a bool, or an instance of a class that contains an expression that will evaluate to a bool within the context that it is used (Expression class includes a string expression which it can parse at runtime and evaluate to a boolean, or error). Equally, I might do something similar with integer, double, DateTime etc.

How could I make a property that would only accept only a value or an instance of an Expression class that will produce that value?

I know that I may be asking something that C# won't natively do, but what would be the closest approximation?


Edit: For additional context, these are propeties of object that are being edited using a PropertyGrid in winforms, so that the user can dropdown to select one of {true/false/Expression}, where Expression opens a dialog for editing an Expression, or the user can type it into the PropertyGrid directly. For this reason, it would destroy the ergonomics to have two properties, as in the user's mind it will just be one.

Upvotes: 0

Views: 345

Answers (2)

Heinzi
Heinzi

Reputation: 172270

Edit: For additional context, these are propeties of object that are being edited using a PropertyGrid in winforms, so that the user can dropdown to select one of {true/false/Expression}, where Expression opens a dialog for editing an Expression, or the user can type it into the PropertyGrid directly. For this reason, it would destroy the ergonomics to have two properties, as in the user's mind it will just be one.

The idiomatic solution in C# would be to create a dedicated class representing the possible choices, e.g.

class UserChoice
{
     public bool? BooleanValue { get; }
     public Expression ExpressionValue { get; }

     // The factory methods ensure that exactly one of BooleanValue
     // and ExpressionValue is not null.

     public static FromBoolean(bool value) => new UserChoice(value, null);

     public static FromExpression(Expression expression)
     {
         if (expression is null)
         {
             throw new ArgumentNullException(nameof(expression));
         }
         return new UserChoice(null, expression);
     }
     
     private UserChoice(bool? booleanValue, Expression expressionValue)
     {
         BooleanValue = booleanValue;
         ExpressionValue = expressionValue;
     }
}

This would be the basic structure. Possible improvements are:

  • Static UserChoice.True and UserChoice.False fields returned by FromBoolean. You now have reference equality for True/False values.
  • An Evaluate method that either returns the Boolean or evaluates the expression.
  • IsBoolean and IsExpression convenience properties.

Upvotes: 2

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186698

Technically, you can achieve the behaviour with a help of implicit operator (see my code below), but do you really want "two types" property? Isn't a better design to have two properties?

// Class that evaluates to bool
public class MyBoolClass {

  // Stab class which evaluates to constant bool
  public class MyBoolClassStub : MyBoolClass {
    internal MyBoolClassStub(bool value) {
      Value = value;
    }

    public bool Value { get; }

    public override bool ToBool() => Value;
  }

  // Stub class which returns true whenever evaluates to bool
  public static MyBoolClassStub EverTrue { get; } = new MyBoolClassStub(true);

  // Stub class which returns flase whenever evaluates to bool
  public static MyBoolClassStub EverFalse { get; } = new MyBoolClassStub(false);

  public virtual bool ToBool() {
    //TODO: here we evaluate to bool; put relevant code
  }

  public static implicit operator bool(MyBoolClass value) =>
    value?.ToBool() ?? throw new ArgumentNullException(nameof(value)); 

  public static implicit operator MyBoolClass(bool value) =>
    value ? EverTrue : EverFalse;
}

Having such a class declaration you can declare property as follow:

private MyBoolClass m_MyProperty;

public MyBoolClass MyProperty {
  get {
    if (m_MyProperty is null)
      return false; //TODO: default value here

    return m_MyProperty;
  }
  set {
    m_MyProperty = value;
  }
}

And use it

// We can assign bool (which will be turned into Stub class)
MyProperty = true;

// We can read bool
if (MyProperty)
  Console.WriteLine("true");

// We can assign "class evaluated to bool"
MyProperty = new MyClass();

Console.WriteLine(MyProperty ? "yes" : "no");

Upvotes: 2

Related Questions