泛型 C# 中的无界类型参数
本文关键字:类型参数 泛型 | 更新日期: 2023-09-27 18:13:25
我是泛型新手,我开始从 MSDN 库学习泛型
我无法理解以下有关无界类型参数的要点。
没有约束的类型参数(如
public class SampleClass<T>{}
中的T
(称为无界类型参数。无界类型参数具有以下规则:
- 不能使用
!=
和==
运算符,因为没有保证 具体类型参数将支持这些运算符。- 您可以与
null
进行比较.如果将无界参数与null
false
,如果类型参数为 值类型。
我没有找到上述几点的任何例子。如果有人给我榜样来理解这些要点,那就太好了。
注意:我的问题是关于使用!=
和==
运算符...为什么我们不能在无界类型中使用这些运算符,如果将无界参数与null
进行比较,为什么总是返回false
<</p>
让我们假设这是可能的:
public class C
{
public bool AreEqual<T>(T first, T second)
{
return first == second;
}
}
现在假设一个名为 F 的struct
,它没有实现 ==
运算符(默认情况下没有结构体(。这里应该怎么做?
F first = new F();
F second = new F();
var c = new C();
Console.WriteLine(c.AreEqual(first, second));
值类型没有==
的实现,我们不能遵从Object.ReferenceEquals
,因为这又是一个值类型。这就是为什么编译器不允许您这样做的原因。
类型参数可以有约束,例如:where T: BaseClass
.这意味着 T 必须继承自 BaseClass
。如果类型参数没有这样的约束,则称为无界。
您引用的文档中有 2 点:
不能使用 != 和 == 运算符,因为不能保证具体类型参数将支持这些运算符。
这意味着编译器无法知道类型T
具有运算符==
和!=
。 SampleClass<int>
可以工作,但如果SampleClass<MyType>
没有实现运算符,MyType
可能不会。 由于T
是无限的,这意味着编译器无法知道预期的内容,因此必须采用最严格的情况。
您可以与空进行比较。如果将无界参数与 null 进行比较,则如果类型参数是值类型,则比较将始终返回 false。
这只是指出您可以与 null 进行比较,但如果 T
是不可为 null 的类型,则它始终返回 false。 请考虑以下事项:
int i = 0;
if (null == i)
{
}
这将生成编译器警告,因为您无法为i
提供任何值以使表达式true
。
我的问题是关于使用 != 和 == 运算符...为什么我们不能在无界类型中使用这些运算符
它们意味着什么?通常!=
和==
的意思是"不等于"和"等于",但其确切含义取决于它们是值类型还是引用类型(以及它们是否重载了这些运算符,但这也适用于许多有界类型(。没有约束,至少要成为这些!=
之一,==
没有任何意义。
以及如果将无界参数与 null 进行比较,为什么总是返回 false。
你看错了。你之前实际说的和引用的,是:
如果类型参数是值类型,则比较将始终返回 false。[强调我的]
这实际上是不正确的,在这种情况下,可为空的值类型可以返回true
:
public class Test<T>
{
public bool IsNull(T val)
{
return val == null;
}
}
使用上面的代码,如果我们调用 new Test<int?>().IsNull(null)
,我们会得到true
,如果我们调用 new Test<int?>().IsNull(1)
,我们会得到 false。
对于除Nullable
类型以外的任何值类型,我们都会得到false
,因为这是唯一可能的值;Nullable<T>
以外的值类型不能为 null。
值得注意的是,抖动会抢占这一点,因为当它为该方法生成机器代码时,它会知道val == null
总是假的,并将代码替换为常量false
。如果有分支,则无需抖动。考虑:
public string CallToString(T val)
{
if (val == null)
return null;
else
return val.ToString();
}
当对于不可为空的值类型的T
进行抖动时,它与代码相同:
public string CallToString(T val)
{
return val.ToString();
}
因为抖动知道第一个分支永远不会被击中。同样考虑Enumerable.Max()
.此方法在可为 null 类型的空序列上返回null
,否则将引发异常。对于特定的覆盖,这很简单:例如,在这种情况下Enumerable.Max(this IEnumerable<decimal?> source)
具有返回 null 的代码,并Enumerable.Max(this IEnumerable<decimal> source)
要抛出的代码。但是,对于一般情况,它需要涵盖这两种情况。它这样做是这样:
public static TSource Max<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
Comparer<TSource> comparer = Comparer<TSource>.Default;
TSource value = default(TSource);
if (value == null)
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
do
{
if (!e.MoveNext()) return value;
value = e.Current;
} while (value == null);
while (e.MoveNext())
{
TSource x = e.Current;
if (x != null && comparer.Compare(x, value) > 0) value = x;
}
}
}
else
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext()) throw Error.NoElements();
value = e.Current;
while (e.MoveNext())
{
TSource x = e.Current;
if (comparer.Compare(x, value) > 0) value = x;
}
}
}
return value;
}
当为可为空的类型(引用类型Nullable<T>
抖动时,抖动事先知道default(TSource) == null
始终为真意味着它与抖动相同:
public static TSource Max<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
Comparer<TSource> comparer = Comparer<TSource>.Default;
TSource value = null;
using (IEnumerator<TSource> e = source.GetEnumerator())
{
do
{
if (!e.MoveNext()) return value;
value = e.Current;
} while (value == null);
while (e.MoveNext())
{
TSource x = e.Current;
if (x != null && comparer.Compare(x, value) > 0) value = x;
}
}
return value;
}
如果该类型是不可为空的值类型,则它与抖动相同:
public static TSource Max<TSource>(this IEnumerable<TSource> source)
{
if (source == null) throw Error.ArgumentNull("source");
Comparer<TSource> comparer = Comparer<TSource>.Default;
TSource value = default(TSource);
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext()) throw Error.NoElements();
value = e.Current;
while (e.MoveNext())
{
TSource x = e.Current;
if (comparer.Compare(x, value) > 0) value = x;
}
}
return value;
}
因此,不可为空的值类型和null
之间的==
始终false
(并且!=
总是true
(这一事实不仅仅是一个限制,它实际上可用于允许我们以不同的方式覆盖可为空和不可为空的类型,并且抖动在删除在特定情况下不使用的分支时将明智地表现。