In this situation, I have a legacy library written in VB.NET, which I am referencing in a C# project. Both are complied for .NET 8, and I'm using nullable reference types in the C# app.
The VB.NET library has an API like so:
Public Shared Async Function LoadBar(Of T)(barGetter As Task(Of T), barReceiver As Action(Of T), tempBar As T) As Task(Of Boolean)
'Give tempBar to barReceiver while waiting for barGetter.
'Then give the resulting 'Bar' from barGetter to barReceiver if the task finishes successfully.
End Function
I then have C# code that consumes this API, similar to the following:
//Gets a Bar that happens to be an int, which is a VALUE type
public Task<int> IntBarGetter() { }
public void IntBarReceiver(int bar) { }
//Gets a Bar that happens to be an string, which is a REFERENCE type
public Task<string> StringBarGetter() { }
public void StringBarReceiver(string? bar) { }
public async void Test()
{
//These two give no trouble
await Foo.LoadBar(IntBarGetter(), IntBarReceiver, -1); //#1
await Foo.LoadBar(StringBarGetter(), StringBarReceiver, "Loading..."); //#2
//This produces warning CS8620
await Foo.LoadBar(StringBarGetter(), StringBarReceiver, null); //#3
}
- For
LoadBar
#1, the inferred type ofT
isint
. - For #2, the inferred type of
T
isstring
. - For #3, the inferred type of
T
isstring?
.
I get a CS8620 warining on #3 with the section StringBarGetter()
underlined:
Argument of type
Task<string>
cannot be used for parameter 'barGetter' of typeTask<string?>
inTask<string?> Foo.LoadBar<string?>(Task<string?> barGetter, Action<string?> barReceiver, string? tempBar)
due to differences in the nullability of reference types.
If I change the signature of StringBarGetter to instead return Task<string?>
(nullable string now) the warning disappears. However, this is code is now factually incorrect as StringBarGetter
will never return null
.
If I rewrote this API in C#, it would look like this:
public static async Task<bool> LoadBar<T>(Task<T> barGetter, Action<T> barReceiver, T tempBar);
The above still gives me the same warning, unless I change the definition by making tempBar
nullable:
public static async Task<bool> LoadBar<T>(Task<T> barGetter, Action<T> barReceiver, T? tempBar);
Now here's my question. I have complete control of both the VB.NET API library and the consuming C# project. Is there anything I can do in either project so that this warning is not triggered? Keeping in mind that I:
- Don't want to make breaking changes to the API.
- Don't want to translate the API itself into C#.
- Don't want to disable the nullable reference types feature.
- Don't want to simply ignore the warning, because later versions of C# might enforce null reference types and trigger actual compile errors. (Plus the warnings are ugly.)
In this situation, I have a legacy library written in VB.NET, which I am referencing in a C# project. Both are complied for .NET 8, and I'm using nullable reference types in the C# app.
The VB.NET library has an API like so:
Public Shared Async Function LoadBar(Of T)(barGetter As Task(Of T), barReceiver As Action(Of T), tempBar As T) As Task(Of Boolean)
'Give tempBar to barReceiver while waiting for barGetter.
'Then give the resulting 'Bar' from barGetter to barReceiver if the task finishes successfully.
End Function
I then have C# code that consumes this API, similar to the following:
//Gets a Bar that happens to be an int, which is a VALUE type
public Task<int> IntBarGetter() { }
public void IntBarReceiver(int bar) { }
//Gets a Bar that happens to be an string, which is a REFERENCE type
public Task<string> StringBarGetter() { }
public void StringBarReceiver(string? bar) { }
public async void Test()
{
//These two give no trouble
await Foo.LoadBar(IntBarGetter(), IntBarReceiver, -1); //#1
await Foo.LoadBar(StringBarGetter(), StringBarReceiver, "Loading..."); //#2
//This produces warning CS8620
await Foo.LoadBar(StringBarGetter(), StringBarReceiver, null); //#3
}
- For
LoadBar
#1, the inferred type ofT
isint
. - For #2, the inferred type of
T
isstring
. - For #3, the inferred type of
T
isstring?
.
I get a CS8620 warining on #3 with the section StringBarGetter()
underlined:
Argument of type
Task<string>
cannot be used for parameter 'barGetter' of typeTask<string?>
inTask<string?> Foo.LoadBar<string?>(Task<string?> barGetter, Action<string?> barReceiver, string? tempBar)
due to differences in the nullability of reference types.
If I change the signature of StringBarGetter to instead return Task<string?>
(nullable string now) the warning disappears. However, this is code is now factually incorrect as StringBarGetter
will never return null
.
If I rewrote this API in C#, it would look like this:
public static async Task<bool> LoadBar<T>(Task<T> barGetter, Action<T> barReceiver, T tempBar);
The above still gives me the same warning, unless I change the definition by making tempBar
nullable:
public static async Task<bool> LoadBar<T>(Task<T> barGetter, Action<T> barReceiver, T? tempBar);
Now here's my question. I have complete control of both the VB.NET API library and the consuming C# project. Is there anything I can do in either project so that this warning is not triggered? Keeping in mind that I:
- Don't want to make breaking changes to the API.
- Don't want to translate the API itself into C#.
- Don't want to disable the nullable reference types feature.
- Don't want to simply ignore the warning, because later versions of C# might enforce null reference types and trigger actual compile errors. (Plus the warnings are ugly.)
1 Answer
Reset to default 0(I got an email notification that someone posted this as an answer, but it's not here anymore so they must have deleted it. They were right though.)
It seems the answer was ridiculously simple. A single character change, in fact. I simply needed to add the null-fiving operator !
to my C# usage.
Changing the method call to either or these fixes the warning:
//The inferred type here is `string?`
await Foo.LoadBar(StringBarGetter()!, StringBarReceiver, null);
//The inferred type here is `string`
await Foo.LoadBar(StringBarGetter(), StringBarReceiver, null!);
I'm used to using the !
operator to specify that a variable or member is not going to return null, but I didn't know you could use it on a literal null
expression, or apparently on generic type to effect the nullability of its type parameter?
I'm not convinced I understand how or why this works, but it does.
Option Strict
in VB on a single file for a case where late binding is needed. – Craig Commented Apr 1 at 13:26LoadBar(Of T As Structure)(barGetter As T, barReceiver As Action(Of T), tempBar As T?)
andLoadBar(Of T As {Class, IEnumerable(Of Char)})(barGetter As T, barReceiver As Action(Of T), tempBar As T)
– Jimi Commented Apr 1 at 19:42