难以定义可空性约束

发布于 2025-01-16 09:18:42 字数 1094 浏览 1 评论 0原文

我有一个名为 TryGetValueAs 的扩展函数,它基本上将 TryGetValue 与强制转换结合在一起。问题是我不断收到可空性警告,但我似乎无法正确处理。

我有以下代码:

using System.Diagnostics.CodeAnalysis;

public static class Extensions
{
    public static bool TryGetValueAs<TOut, TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, [MaybeNullWhen(false)] out TOut value)
        where TOut : TValue
    {
        if (dictionary.TryGetValue(key, out var v))
        {
            value = (TOut)v!;
            return true;
        }
        else
        {
            value = default;
            return false;
        }
    }
}

public interface IFoo {}
public class Foo : IFoo {}

class Program
{
    public static void Main(string[] args)
    {
        var dict = new Dictionary<string, IFoo>();
        if (dict.TryGetValueAs("foo", out Foo foo))
        {
            Console.WriteLine(foo.ToString());
        }
    }
}

我尝试将 out Foo foo 更改为 out Foo? foo,但这只会导致更多警告。如何编写此函数,以便它正确处理可为空性,同时与值和引用兼容?

I have an extension function called TryGetValueAs which basically combines TryGetValue with a cast. The problem is that I keep getting nullability warnings and I can't seem to get it right.

I have the following code:

using System.Diagnostics.CodeAnalysis;

public static class Extensions
{
    public static bool TryGetValueAs<TOut, TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, [MaybeNullWhen(false)] out TOut value)
        where TOut : TValue
    {
        if (dictionary.TryGetValue(key, out var v))
        {
            value = (TOut)v!;
            return true;
        }
        else
        {
            value = default;
            return false;
        }
    }
}

public interface IFoo {}
public class Foo : IFoo {}

class Program
{
    public static void Main(string[] args)
    {
        var dict = new Dictionary<string, IFoo>();
        if (dict.TryGetValueAs("foo", out Foo foo))
        {
            Console.WriteLine(foo.ToString());
        }
    }
}

I tried changing out Foo foo to out Foo? foo, but that only results in more warnings. How can I write this function so that it correctly handles the nullability, while also being compatible with both values and references?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

饮惑 2025-01-23 09:18:42

这是我的尝试:

public static bool TryGetValueAs<TOut, TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary, 
    TKey key, 
    [NotNullWhen(true)] out TOut? value)
    where TOut : TValue
{
    if (dictionary.TryGetValue(key, out var v)
        && v is TOut)
    {
        value = (TOut)v;
        return true;
    }

    value = default;
    return false;
}

注意:

  1. out TOut value -> 出 TOut? value - 这是必需的,因为此方法可以返回 null。
  2. [MaybeNullWhen(false)] 更改为 [NotNullWhen(true)] - 这告诉编译器 TryGetValueAs 返回 true,然后 foo< /code> 不为空。我认为这比 MaybeNullWhen 更好地代表了这个函数。当我使用 foo.GetType() 并且没有收到编译器警告时,我们可以看到此更改的好处;
  3. 添加了 && v 是 TOut,用于处理所需类型与实际类型不匹配的情况。虽然这与这个问题的主题并不严格相关,但我认为这是一个大问题: Try 方法确实不应该抛出。

让我们测试一下:

var dict = new Dictionary<string, IFoo>()
{
    {"food", new Food()},
    {"foot", new Foot()},
    {"bigfoot", new BigFoot()},
};

Try<Food>("food");
Try<Foot>("food");
Try<Food>("foot");
Try<Foot>("foot");
Try<Foot>("bigfoot");

void Try<T>(string key) where T: IFoo
{
    if(dict.TryGetValueAs(key, out T? foo))
        Console.WriteLine($"Success! '{key}' retrieved as {typeof(T)} (is:{foo.GetType()}), value: '{foo}'");
    else
        Console.WriteLine($"Unable to retrieve '{key}' as {typeof(T)}");
}

public interface IFoo {}
public class Food : IFoo {}
public class Foot : IFoo {}
public class BigFoot : Foot {}

输出看起来不错:

Success! 'food' retrieved as Food (is:Food), value: 'Food'
Unable to retrieve 'food' as Foot
Unable to retrieve 'foot' as Food
Success! 'foot' retrieved as Foot (is:Foot), value: 'Foot'
Success! 'bigfoot' retrieved as Foot (is:BigFoot), value: 'BigFoot'

Here is my attempt:

public static bool TryGetValueAs<TOut, TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary, 
    TKey key, 
    [NotNullWhen(true)] out TOut? value)
    where TOut : TValue
{
    if (dictionary.TryGetValue(key, out var v)
        && v is TOut)
    {
        value = (TOut)v;
        return true;
    }

    value = default;
    return false;
}

Notes:

  1. out TOut value -> out TOut? value - this is required because this method can return null.
  2. Changed [MaybeNullWhen(false)] to [NotNullWhen(true)] - this tells the compiler that TryGetValueAs returns true then foo is not null. I think this better represents this function than MaybeNullWhen. We can see the benefit of this change when I use foo.GetType() and don't get a complier warning;
  3. Added && v is TOut to handle the case when the desired and actual type don't match. While this is not strictly related to the topic of this question I think this a big one: Try method really should not throw.

Let's test it:

var dict = new Dictionary<string, IFoo>()
{
    {"food", new Food()},
    {"foot", new Foot()},
    {"bigfoot", new BigFoot()},
};

Try<Food>("food");
Try<Foot>("food");
Try<Food>("foot");
Try<Foot>("foot");
Try<Foot>("bigfoot");

void Try<T>(string key) where T: IFoo
{
    if(dict.TryGetValueAs(key, out T? foo))
        Console.WriteLine(
quot;Success! '{key}' retrieved as {typeof(T)} (is:{foo.GetType()}), value: '{foo}'");
    else
        Console.WriteLine(
quot;Unable to retrieve '{key}' as {typeof(T)}");
}

public interface IFoo {}
public class Food : IFoo {}
public class Foot : IFoo {}
public class BigFoot : Foot {}

The output looks good:

Success! 'food' retrieved as Food (is:Food), value: 'Food'
Unable to retrieve 'food' as Foot
Unable to retrieve 'foot' as Food
Success! 'foot' retrieved as Foot (is:Foot), value: 'Foot'
Success! 'bigfoot' retrieved as Foot (is:BigFoot), value: 'BigFoot'
一花一树开 2025-01-23 09:18:42

如果我正确理解逻辑,那么使用 NotNullWhenAttributetrue 返回值与 TOut? value 参数应该可以解决问题:

public static class Extensions
{
    public static bool TryGetValueAs<TOut, TKey, TValue>(this IDictionary<TKey, TValue> dictionary, 
        TKey key, 
        [NotNullWhen(true)] out TOut? value) where TOut : TValue
    {
        if (dictionary.TryGetValue(key, out var v))
        {
            value = (TOut)v!;
            return true;
        }

        value = default;
        return false;
    }
}

if (dict.TryGetValueAs("foo", out Foo? foo))
{
    Console.WriteLine(foo.ToString()); // no warnings
}
else
{
    Console.WriteLine(foo.ToString()); // warning
}

请注意,您可能不会执行类型测试,也不会直接进行强制转换,即使用类似以下内容的 value = (TOut)v!; 代替:

if (dictionary.TryGetValue(key, out var v) && v is TOut result)
{
    value = result;
    return true;
}
...

If I understand the logic correctly then using NotNullWhenAttribute for true return value with TOut? value argument should do the trick:

public static class Extensions
{
    public static bool TryGetValueAs<TOut, TKey, TValue>(this IDictionary<TKey, TValue> dictionary, 
        TKey key, 
        [NotNullWhen(true)] out TOut? value) where TOut : TValue
    {
        if (dictionary.TryGetValue(key, out var v))
        {
            value = (TOut)v!;
            return true;
        }

        value = default;
        return false;
    }
}

if (dict.TryGetValueAs("foo", out Foo? foo))
{
    Console.WriteLine(foo.ToString()); // no warnings
}
else
{
    Console.WriteLine(foo.ToString()); // warning
}

Note that probably you will won't to perform type test and not cast directly, i.e. instead of value = (TOut)v!; use something like :

if (dictionary.TryGetValue(key, out var v) && v is TOut result)
{
    value = result;
    return true;
}
...
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文