1

The following does not compile:

public struct Foo
{
    public static implicit operator Foo(string bar)
    {
        return new Foo();
    }

    public static implicit operator Foo(long? bar)
    {
        return new Foo();
    }

    public static void Test()
    {
        Foo bar = 0;
        Foo bar2 = (long?)null;
        Foo bar3 = "";
        Foo bar4 = null; // Cannot convert null to 'Foo' because it is a non-nullable value type
    }
}

'Foo bar4 = null' fails, presumably because the compiler doesn't know which implicit operator to use, because changing the operator from long? to long causes that line to compile but instead 'Foo bar2 = (long?)null' fails instead (requiring an explicit cast).

My question is; Is there a way to make 'Foo bar4 = null' work as well, or is this just a limitation of the language (that I cannot add a 'null' operator or tell it which one to use for null for instance)?

I realize I could change the struct to a class, but I don't want it to be null, I want to be able to have null create an instance of it.

EDIT: I should add that I understand there are a lot of ways to get around this, but since '= null' (doing 'new Foo()' essentially) works with only one of the implicit operators, I'm just wondering if it's possible to have it still work with both of them there (I feel like there should be a way in the language to do this - either now or in the future, no?).

Michael Olsen
  • 404
  • 3
  • 14
  • 3
    `Is there a way to make 'Foo bar4 = null' work as well` <= No, you can't do that. – Igor Sep 20 '18 at 15:04
  • Your struct isn´t nullable because you introduce a cast that enables to cast a null-value to that type. In fact a `struct` by itself is *never* null, however references to it *may* be, but only when they are of type `Nullable`. – MakePeaceGreatAgain Sep 20 '18 at 15:05
  • 1
    Yes, if you want a value type to be `null` make it a `Nullable<>`. If you want to treat it as `new Foo()`(like you used in the implicit operators) use `Foo bar4 = default(Foo);`. On this way you get `null` if it was a `class` and `new Foo()` if it is a `struct`. – Tim Schmelter Sep 20 '18 at 15:08
  • 1
    For me it doesn't make much sense. If during writing your code you know that you need to assign default (or whatever value you assign for `(long?)null`) to `Foo`, you can just assign it. – Yeldar Kurmangaliyev Sep 20 '18 at 15:09
  • You could write a thrid overload with an `object` parameter which you can check for null. If any existing object gets assigned, throw an exception. Downside: the compiler doesn't warn you if you assign an object (obviously). Something like this: `public static implicit operator Foo(object bar) { if (bar != null) throw new Exception(); return new Foo(); } ` – Emaro Sep 20 '18 at 15:22
  • I edited my question a bit to make it clear I just want to treat the "setting it as null" as new Foo() - but with both of the implicit operators there. – Michael Olsen Sep 20 '18 at 15:23
  • If you can somehow make `int x = null;` compile, you can probably do this. :) – CodeHxr Sep 20 '18 at 15:27
  • @Emaro unfortunately that doesn't compile. "User-defined conversions to or from a base class are not allowed". – Michael Olsen Sep 20 '18 at 15:34
  • How shoukd your program know, which of the implicit operators to run when using `Foo bar4 = null;`? The string-conversion? Or the `float?`-one? Or even a third one? – MakePeaceGreatAgain Sep 20 '18 at 21:47

3 Answers3

3

My question is; Is there a way to make 'Foo bar4 = null' work as well, or is this just a limitation of the language (that I cannot add a 'null' operator or tell it which one to use for null for instance)?

Based on this question and your edit, you are basically asking

Why does Foo bar4 = null compile when it does not if I add another implicit cast operator?

The answer is simple. null without any context is typeless and so the compiler does not know which operator to use. Why? Well the overload resolution algorithm underpinning the language does not examine the type of the thing you are trying to assign null to, so it doesn't know which operator you intended.

You could argue that a future language spec could be modified to do this extra analysis, but the team probably considers that not worth the effort or it would be a breaking change.

The best that you can do is to avoid the casting in this case. You can, depending on your C# level, do either of these:

  • Foo bar4 = default;
  • Foo bar4 = default(Foo);

which results in a useable Foo. The two default expressions and new Foo() are all equivalent. They all result in a struct with all its fields zeroed out.

For more information you can see the default value expressions section of the C# programming guide.

And finally, while you can get away with a single implicit cast operator that casts a null to a struct, it doesn't mean you should. Someone reading that code not knowing about the cast would probably be questioning their sanity for a few minutes. It's best not to stray from idiomatic code if you can.

Kit
  • 20,354
  • 4
  • 60
  • 103
  • 1
    Out of curiosity, is `default` equivalent to `new Foo()`, given that a struct is always given a default parameterless constructor that does nothing? – CodeHxr Sep 20 '18 at 15:21
  • Yes they're equivalent. The implicit constructor and the two default expressions result in all the fields of the struct being zeroed out. – Kit Sep 20 '18 at 15:28
  • Using default is a good alternative yes. I'm wondering why it's a limitation on purpose though in the case of these two operators, when one works fine. Do you have a reference where I can read up on it? – Michael Olsen Sep 20 '18 at 15:32
  • If you're assigning a value that "might" be null, you could do something like `Foo bar4 = valueThatMightBeNull ?? default;`, but I don't know that this is what you're looking for either... – CodeHxr Sep 20 '18 at 15:48
  • @Kit sorry I meant a reference on why it's a limitation of the language on purpose that I can't have two operators like that (or more) :) – Michael Olsen Sep 20 '18 at 15:58
  • Took me a while to get my head around what you were asking, check my answer now. – Kit Sep 20 '18 at 17:22
  • Thanks, that's perfect :) – Michael Olsen Sep 23 '18 at 16:33
2

Your struct isn´t nullable because you introduce a cast that enables to cast a null-value to that type. A cast in itself is just a member that doesn´t change the types semantics.

In fact a struct by itself is never null, however references to it may be, but only when they are of type Nullable<Foo>. So bar4 needs to be of type Foo?, which is the same as Nullable<Foo>.

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
0

If I'm not mistaken, a struct can never be assigned a value of null because it's a value-type, similar to int, bool, and DateTime.

You could, however, use a Nullable<T> like so:
Foo? bar4 = null;
or
Nullable<Foo> bar4 = null;

Make sure you treat it like any other Nullable<T> and check .HasValue before referencing bar4.Value to avoid the wonderful NullReferenceException.

CodeHxr
  • 855
  • 8
  • 17
  • Sorry, yeah - I don't want to set it null, I want null to essentially create Foo - without doing Foo?. And this with both of the implicit operators. – Michael Olsen Sep 20 '18 at 15:26