1
string o=null;
Console.WriteLine($"Hello World '{o}'");

This outputs:

Hello World ''

I would like to explicitly write "null" for null values.

string o=null;
Console.WriteLine($"Hello World '{o??"null"}'");

This does just that:

Hello World 'null'

But if o is not of type string (or Object) it generates a compilation error. For example:

Array o=null;
Console.WriteLine($"Hello World '{o??"null"}'");

Compilation error Operator '??' cannot be applied to operands of type 'Array' and 'string'

What is the best way to achieve the desired outcome? It's a shame you cannot modify how $ handles null as it appears hard-coded to use String.EmptyString

Super Jade
  • 5,609
  • 7
  • 39
  • 61
Mr. Boy
  • 60,845
  • 93
  • 320
  • 589
  • 2
    `$"{o?.ToString() ?? "null"}"`? – juharr Jul 15 '20 at 15:54
  • @juharr yuck! That seems to undo the whole benefit of `$`. Could this be pulled out to a utility method somehow? It's a shame you cannot modify how `$` handles `null` as it appears hard-coded to use `String.EmptyString` – Mr. Boy Jul 15 '20 at 15:57
  • 1
    @Mr.Boy Well, outputting "null" is not a common requirement. That's not usually a word you want an end user to ever see. – Gabriel Luci Jul 15 '20 at 16:00
  • and even if you do, you can always override ToString – Bizhan Jul 15 '20 at 16:02
  • @Bizhan how can you override `ToString` for a null object? Is that allowed? – Mr. Boy Jul 15 '20 at 16:03
  • You're right, it's not allowed. and {o} will return empty string. I guess that's why we have ? and ?? – Bizhan Jul 15 '20 at 16:07
  • 1
    @Mr.Boy You can always make it an extension method and call that instead ie `public static string ToStringWithNullOutput(this object input) => input is null ? "null" : input.ToString();` Then your calling code is clear in the semantics, no one will get unexpected behavior. `Console.WriteLine($"Hello World '{oToStringWithNullOutput()");` – asawyer Jul 15 '20 at 16:10
  • HOWEVER!, you can write an extension method. something like ToStringWithNullCheck – Bizhan Jul 15 '20 at 16:10
  • @asawyer I don't see a reason not to provide that as an answer. – Mr. Boy Jul 15 '20 at 17:02

3 Answers3

5

You can cast "null" to object so that ?? can be applied to all types of operands.

$"Hello World '{o ?? (object)"null"}'"
Bizhan
  • 16,157
  • 9
  • 63
  • 101
1

You can leverage that $ can turn your string into a formattablestring and you can provider a custom formatted that it will call when it processes each arg in turn. Like providing a custom comparer to a sort function


class NullyFormatProvider : IFormatProvider
{
    private readonly NullyFormatter _formatter = new NullyFormatter();

    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter))
            return _formatter;
        return null;
    }

    class NullyFormatter : ICustomFormatter
    {
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            if (arg == null)
                return "arg was null, bro!";
            else
                return arg.ToString();
        }
    }
}

You can trigger your null format provider by passing it through a function that will make c# treat it as a formattable string (rather than the compiler straight calling string format on it for example) :

static string NullyVersion(FormattableString formattable)
{
    return formattable.ToString(new NullyFormatProvider());
}

...

Array o = null;
string txt = NullyVersion($"check out this array: {o}");

Of course, you wouldn't make it this lengthy/you probably wouldn't use NullyVersion to create a string to use where you wanted a string.. you'd make your e.g. "logging method that takes a string" take a FormattableString instead and then format it with the nully formatter perhaps like:

static string Log(FormattableString formattable)
{
    Console.WriteLine( formattable.ToString(new NullyFormatProvider()); //or some instance of NFP
}

Then you can just use in your code like you wanted at the outset:

Array o = null;
Log($"Data was {o}");

I haven't looked too deep into how to check whether you're passed something that takes a format - you'll note that the Format() method in the ICustomFormatter takes a string format - if you wrote Log($"it is now {DateTime.Now:yyyyMMdd} woo") then the object arg would be the datetime, and the string format would contain "yyyyMMdd" - it can be anything you want. You could define your own:

int[] nums = {1,2,3};
Log($"those nums are {nums:csv}");

And in your Format:

if(format == "csv" && arg is int[] x)
  //turn the passed in arg (an int array inside an obj) into some csv representation...
  return string.Join(",", x.Select(e => e.ToString()));

For more details take a look at ICustomFormatter https://learn.microsoft.com/en-us/dotnet/api/system.icustomformatter?view=netcore-3.1

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • I'd be interested why this was down-voted without explanation, it's a non-trivial amount of work to write this, – Mr. Boy Jul 15 '20 at 17:01
  • We may never find out; the debates as to whether "comment as to what could be improved should be mandatory to cast a DV" are lengthy and go round and round.. it could be for something as trivial as I didn't answer your question because I didn't code a formatter that return exactly and only "null". most important thing is to hopefully answer your question and let you know that "no, you don't have to put up with the default formatter - you can write your own". You could perhaps even defer to the default one in all the cases where your object isn't null and just return the "null" when it is.. – Caius Jard Jul 15 '20 at 19:31
0

I'm not sure what you want to print if your variable is not null, but you could try the ternary conditional operator

Array o = null;
Console.WriteLine($"Hello World '{(o == null ? "null" : "not null")}'");

or

Array o = null;
Console.WriteLine($"Hello World '{(o == null ? "null" : o.ToString())}'");

depending on what o is and whether you have overridden ToString().


Related:

How to print the null values if the dictionary has it using LINQ

Super Jade
  • 5,609
  • 7
  • 39
  • 61