C# 中的“相等判断”
C# 中的“相等判断”
C# 中判断相等的方式很多,例如:
- 双等号
==
- 实例的
Equals()
方法 -
Object.Equals()
静态方法 -
Object.ReferenceEquals()
方法 -
EqualityComparer<int>.Default.Equals()
方法 -
is
运算符
还有一些特殊的类型内部实现了相等判断,例如:
- 元组
- 匿名类型
还有一些特别的相等判断,例如:
- 内部元素结构化相等比较
这些相等判断都是做什么的?何时使用?下面我将一一列举说明。
1. 双等号(==
)的相等判断
1.1 基元类型
对于基元类型(int
、float
等),==
是比较二者值是否相等。查看这些基元类型的源码可以发现,它们并没有对 ==
运算符进行重载,但它们却可以使用 ==
进行比较。这是因为编译器对基元类型进行了特殊优化:
- 编译阶段:直接生成
ceq
IL 指令(Compare Equal) - 运行时:JIT 编译器将
ceq
转换为底层 CPU 的整数比较指令,无需调用任何方法
1.2 部分预定义值类型
部分内置的值类型(非基元类型,如 decimal
),它们可以使用 ==
进行比较,是因为对 ==
运算符进行了重载。下面两段代码比较了 int
和 decimal
进行相等比较对应的 IL 代码:
int num = 1;
int num2 = 2;
bool flag = num == num2;
// 对应的 IL 代码如下,有删减
IL_0000: nop
// int num = 1;
IL_0001: ldc.i4.1
IL_0002: stloc.0
// int num2 = 2;
IL_0003: ldc.i4.2
IL_0004: stloc.1
// bool flag = num == num2;
IL_0005: ldloc.0
IL_0006: ldloc.1
IL_0007: ceq
IL_0009: stloc.2
decimal num = 1m;
decimal num2 = 2m;
bool flag = num == num2;
// 对应的 IL 代码如下,有删减
IL_0000: nop
// decimal num = 1m;
IL_0001: ldsfld valuetype [System.Runtime]System.Decimal [System.Runtime]System.Decimal::One
IL_0006: stloc.0
// decimal num2 = 2m;
IL_0007: ldloca.s 1
IL_0009: ldc.i4.2
IL_000a: call instance void [System.Runtime]System.Decimal::.ctor(int32)
// bool flag = num == num2;
IL_000f: ldloc.0
IL_0010: ldloc.1
IL_0011: call bool [System.Runtime]System.Decimal::op_Equality(valuetype [System.Runtime]System.Decimal, valuetype [System.Runtime]System.Decimal)
IL_0016: stloc.2
对于值类型,如果没有重载 ==
运算符,是无法使用 ==
进行比较的。如下代码无法通过编译:
Person person1 = new Person();
Person person2 = new Person();
bool flag = person1 == person2;
struct Person
{
public int Age { get; set; }
}
1.3 引用类型
对于引用类型,==
判断返回二者的引用是否为相同(前提是未重载 ==
运算符)。最典型的是 string
类型,它的 ==
方法判断两个字符串内容是否相同,将其强转为 object
类型再进行 ==
比较时,将转化为引用比较。以如下代码为例,它将依次输出 True、False:
string value = "123";
string content1 = value + value;
string content2 = value + value;
Console.WriteLine(content1 == content2);
Console.WriteLine((object)content1 == (object)content2);
上述 string
变量值是在运行时才确定的,因此它们的引用不相同。再看下面这段代码,它将依次输出 True、True:
string value = "123";
string content1 = value;
string content2 = value;
Console.WriteLine(content1 == content2);
Console.WriteLine((object)content1 == (object)content2);
这是因为编译时 string
变量的值已经确定了,为了节省内存,二者是同一实例。
2. Equals()
实例方法
2.1 Equals()
的编写准则与使用场景
Equals()
实例方法常用于哈希表等需要执行相等判断的集合类型,因此它对相等判断的要求更为严格。它遵循如下准则:
自反性
x.Equals(x)
应该为true
。对称性
x.Equals(y)
的返回值与y.Equals(x)
相同。传递性
x.Equals(y)
、y.Equals(z)
为true
,则x.Equals(z)
也应该为true
一致性
只要 x 和 y 未被修改,
x.Equals(y)
的返回值都应该相同。非空性
x.Equals(null)
应返回false
。
如果不严格遵守上述准则,那么哈希表中的工作就会出错!假设我们有如下 Person
类,它覆写了 Equals()
方法,并且始终返回 false,这使得它在插入 HashSet<T>
后,却无法正确查找。如下代码将输出 False:
Person person = new Person
{
Name = "John",
Age = 20
};
HashSet<Person> set = new();
set.Add(person);
Console.WriteLine(set.Contains(person));
struct Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object obj)
{
return false;
}
}
以上是 Equals()
方法的主要用途(我很少在业务代码中看到调用 Equals()
方法的),下面我们讲解 Euqals()
在值类型、引用类型中的不同。
2.1 基元类型
基元类型的 Equals()
方法直接调用了 ==
运算符,因此它的调用和使用 ==
运算符没有差别。
2.1.1 NaN 的相等判断
在学习 NaN 的相等判断之前我们先回答一个问题:NaN 是什么?
NaN 是“Not a Number”的缩写,表示未定义或不可表示的值。它常出现在浮点计算被除数为 0 的情况。如下计算便会得到 NaN:
Console.WriteLine(0.0 / 0.0);
NaN 十分特殊,使用 ==
运算符进行判断和使用 Equals()
进行判断结果刚好相反!以如下代码为例,它将分别输出 False、True
double value = 0.0 / 0.0;
Console.WriteLine(value == value);
Console.WriteLine(value.Equals(value));
这是因为 ==
更多的是表示“数学中的相等”,在数学中,两个 NaN 无论如何都不可能相等,因此 value == value
的结果是 False。而 Equals()
必须支持自反性,因此 value.Equals(value)
的结果是 True。集合和字典需要 Equals()
保持这个行为,否则就无法找到之前存储的项目了。
我们通常使用 floast.IsNaN()
或 double.IsNaN()
方法判断一个值是否为 NaN:
Console.WriteLine (double.IsNaN (0.0 / 0.0));
Info
请参考第2章 C# 语言基础 - hihaojie - 博客园 2.4.7 特殊的浮点值、第6章 框架基础 - hihaojie - 博客园 6.11.2.6
Equals
和==
在何时并不等价
2.2 值类型
这里我们特指自定义结构体,其 Equals()
方法未被覆写。
值类型的 Equals()
方法很特别,它的底层会通过反射对所有字段进行相等比较!以如下代码为例,即使 p1
和 p2
分别定义,Equals()
方法仍能判断二者相等。
Person p1 = new Person
{
Name = "John",
Age = 18
};
Person p2 = new Person
{
Name = "John",
Age = 18
};
Console.WriteLine(p1.Equals(p2));
struct Person
{
public string Name { get; set; }
public int Age { get; set; }
}
通过反射完成相等判断,它的性能必然受限,并且值类型在使用 object.Equals()
方法比较时会进行装箱。为此 C# 提供了 IEquatable<T>
接口。
2.2.1 IEquatable<T>
接口
Info
该内容可参考第4章 类型设计准则 - hihaojie - 博客园的 4.7 struct 的设计、第8章 使用准则 - hihaojie - 博客园的 8.6
IComparable<T>
与IEquatable<T>
、8.9 Object
值类型实现 IEquatable<T>
可以避免 2 个问题:
- 值类型的
Object.Equals()
方法会导致装箱, -
Object.Equals()
使用了反射,它的默认实现效率不高。
该接口定义如下,其 Equals()
方法要求我们自行完成成员的相等判断:
public interface IEquatable<T>
{
bool Equals(T? other);
}
以前文的 Person
结构体为例,它的实现如下:
Person p1 = new Person
{
Name = "John",
Age = 18
};
Person p2 = new Person
{
Name = "John",
Age = 18
};
Console.WriteLine(p1.Equals(p2));
class Person : IEquatable<Person>
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object? obj)
{
return Equals(obj as Person);
}
public bool Equals(Person? other)
{
if (other is null)
{
return false;
}
if (!Name.Equals(other.Name))
{
return false;
}
if (Age != other.Age)
{
return false;
}
return true;
}
}
2.3 引用类型
对于引用类型(在 Equals()
方法、==
运算符未被覆写的情况下),其 Equals()
方法与 ==
运算符含义相同,比较引用是否相同。
而 Equals
和 ==
含义不同这种做法在引用类型中有很多,开发者自定义 Equals()
实现值的相等比较,而仍旧令 ==
执行(默认的)引用相等比较。StringBuilder
类便采用了这种方式,如下代码将输出“False、True”:
var sb1 = new StringBuilder ("foo");
var sb2 = new StringBuilder ("foo");
Console.WriteLine (sb1 == sb2);
Console.WriteLine (sb1.Equals (sb2));
Notice
StringBuilder
并未覆写object.Equals()
实例方法,它只是添加了一个新的重载方法。因此如下两段代码执行结果不同:Console.WriteLine(sb1.Equals(sb2));
Console.WriteLine(((object)sb1).Equals(sb2));
什么情况下应该覆写引用类型的 Equals()
方法呢?《框架设计指南》第8章 使用准则给出了建议:
CONSIDER
:如果引用类型表示的是一个值,考虑覆盖Equals()
方法以提供值相等语义。例如:表示数值、数学实体的引用类型。
Info
更多内容,请参考第6章 框架基础 - hihaojie - 博客园 6.11.2.6
Equals
和==
在何时并不等价、第8章 使用准则 8.9.1 Object.Equals
3. Object.Equals()
静态方法
Object.Equals()
静态方法主要用于避免“实例为空导致的空引用异常(NullReferenceException
)”。它的内部操作如下:
public static bool Equals (object objA, object objB)
=> objA == null ? objB == null : objA.Equals (objB);
与 object.Equals()
实例方法不同,该静态方法接受两个参数。它常用于 ==
和 !=
无法使用的场景,譬如泛型实例比较:
class Test<T>
{
T _value;
public void SetValue(T newValue)
{
if (!object.Equals(newValue, _value))
{
_value = newValue;
OnValueChanged();
}
}
protected virtual void OnValueChanged() {}
}
上述代码无法使用 ==
和 !=
(因为类型不确定,编译时无法绑定);对于 object.Equals()
实例方法,如果 newValue
为 null,则会抛出 NullReferenceException
异常,因此这里使用静态方法 Object.Equals()
。
4. Object.ReferenceEquals()
方法
我们在2.3 引用类型提到:
而
Equals
和==
含义不同这种做法在引用类型中有很多,开发者自定义Equals()
实现值的相等比较,而仍旧令==
执行(默认的)引用相等比较。StringBuilder
类便采用了这种方式,如下代码将输出“False、True”:
如果 ==
和 Equals()
都进行了重载,我们又需要判断引用是否相同,应该怎么做?
有 2 种方案:
- 将实例显式转换为
object
再用==
进行比较 - 通过
Object.ReferenceEquals()
静态方法比较
我们实际看 Object.ReferenceEquals()
代码会发现,上述两种方案其实是一样的:都是将实例转换为 object
再用 ==
进行比较,只是实例转换为 object
这一步通过传参的方式省略了:
public static bool ReferenceEquals (Object objA, Object objB)
{
return objA == objB;
}
如下这段代码对比了各种比较方式,只有最后两条输出语句正确进行了引用比较:
var p1 = new Person
{
Name = "John",
Age = 18
};
var p2 = new Person
{
Name = "John",
Age = 18
};
Console.WriteLine(p1 == p2);
Console.WriteLine(p1.Equals(p2));
Console.WriteLine(((object)p1).Equals(p2));
Console.WriteLine(object.Equals(p1, p2));
Console.WriteLine((object)p1 == (object)p2);
Console.WriteLine(object.ReferenceEquals(p1, p2));
class Person : IEquatable<Person>
{
public string Name { get; set; }
public int Age { get; set; }
public static bool operator ==(Person left, Person right)
{
return left.Equals(right);
}
public static bool operator !=(Person left, Person right)
{
return !(left == right);
}
public override bool Equals(object? obj)
{
return Equals(obj as Person);
}
public bool Equals(Person? other)
{
if (other is null)
{
return false;
}
if (!Name.Equals(other.Name))
{
return false;
}
if (Age != other.Age)
{
return false;
}
return true;
}
}
5. EqualityComparer<T>.Default.Equals()
方法
我们在3. Object.Equals() 静态方法的用例代码中展示了两个泛型实例比较是否相等:
class Test<T>
{
T _value;
public void SetValue(T newValue)
{
if (!object.Equals(newValue, _value))
{
_value = newValue;
OnValueChanged();
}
} protected virtual void OnValueChanged() {}
}
它虽然实现了功能,但性能上仍有部分损耗:如果 T 是值类型,使用 object.Equals()
比较的过程中会发生装箱!EqualityComparer<T>.Default.Equals()
方法便应运而生。
EqualityComparer<T>.Default
属性会返回一个通用的相等比较器,替代静态的 object.Equals
方法。它会首先检查 T
是否实现了 IEquatable<T>
,实现了则直接调用实现类,从而避免装箱开销。
如下代码改为了使用 EqualityComparer<T>.Default.Equals()
,避免了装箱:
class Test<T>
{
T _value;
public void SetValue(T newValue)
{
if (!EqualityComparer<T>.Default.Equals(newValue, _value))
{
_value = newValue;
OnValueChanged();
}
}
protected virtual void OnValueChanged() { }
}
Info
更多内容,请参考第7章 集合 - hihaojie - 博客园 7.7.1
IEqualityComparer
和EqualityComparer
6. is
运算符
is
运算符可用的模式有三种:常量模式、类型模式、var模式。这里我们讲解常量模式涉及的相等比较。
6.1 常量模式
使用 is 与常量比较相等时,有两种情况:
- 整型表达式:使用
==
进行比较 - 其他类型:使用
object.Equals()
静态方法进行比较。
6.1.1 整型表达式
整型表达式会转为使用 ==
运算符进行比较。以如下代码为例,反编译生成的程序可以看到,它实际使用 ==
进行比较:
long x = 10L;
if (x is 10)
{
Console.WriteLine("x is 10");
}
long x = 10L;
if (x == 10)
{
Console.WriteLine("x is 10");
}
6.1.2 其他类型
我们可用通过 is 判断变量是否为 null,此时相等比较使用的是 object.Equals()
方法:
- 检查是否为
null
,如下例所示:
if (input is null)
{
return;
}
将表达式与 null
匹配时,编译器保证不会调用用户重载的 ==
或 !=
运算符。
- 可使用否定模式执行非 null 检查,如下例所示:
if (result is not null)
{
Console.WriteLine(result.ToString());
}
Question
请思考如下代码,会输出什么内容?符合上述情况中的哪种?
Match(10L); static void Match(object input)
{
if (input is 10)
Console.WriteLine("input 是整型类型的 10");
else
Console.WriteLine("Input 不是整型类型的 10");
}
答案是第二种。在传入
10L
的值时发生了装箱,因此input is 10
实际调用了object.Equals()
方法比较相等,显然,object.Equals(10L, 10)
的结果是 False
6.2 列表模式
- 从 C#11 开始,可以使用列表模式来匹配列表或数组的元素。 以下代码检查数组中处于预期位置的整数值:
int[] empty = [];
int[] one = [1];
int[] odd = [1, 3, 5];
int[] even = [2, 4, 6];
int[] fib = [1, 1, 2, 3, 5];
Console.WriteLine(odd is [1, _, 2, ..]); // false
Console.WriteLine(fib is [1, _, 2, ..]); // true
Console.WriteLine(fib is [_, 1, 2, 3, ..]); // true
Console.WriteLine(fib is [.., 1, 2, 3, _ ]); // true
Console.WriteLine(even is [2, _, 6]); // true
Console.WriteLine(even is [2, .., 6]); // true
Console.WriteLine(odd is [.., 3, 5]); // true
Console.WriteLine(even is [.., 3, 5]); // false
Console.WriteLine(fib is [.., 3, 5]); // true
Info
更多内容,请参考
is
运算符 - 将表达式与类型或常量模式匹配 - C# reference | Microsoft Learn 、《深入理解C#》第4版 12.4.1 常量模式、你不知道的C#冷知识(其二)_哔哩哔哩_bilibili
7. IEqualityComparer<T>
和 EqualityComparer<T>
提到相等比较必然会谈到哈希表。前文我们也提到了 Equals()
方法对哈希表的重要性。如果你有仔细观察过 HashSet<T>
和 Dictionary<TKey, TValue>
的构造方法,会发现它们都有接收 IEqualityComparer<T>
接口实例的构造器:
public HashSet([Nullable(IEqualityComparer<T>)
public Dictionary(IEqualityComparer<TKey>)
我们从哈希表的原理可知:存放元素时,哈希表通过 GetHashCode()
方法获取哈希值,将元素存放至相应位置;查找元素时,哈希表通过 GetHashCode()
方法获取哈希值,获取元素,再调用 Equals()
方法确认是否为要查找的元素。
有时我们想自定义哈希表的存放、查找规则,便需要使用 IEqualityComparer<T>
接口。该接口定义如下:
public interface IEqualityComparer<in T>
{
bool Equals(T x, T y);
int GetHashCode(T obj);
}
接下来我们演示一下该接口的使用。我们假设有 Person
类:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
我们希望只要 Person
实例的 Name
和 Age
相同,就认为是同一个“人”,下面这段代码显然不符合要求,它会输出 False:
HashSet<Person> set = new HashSet<Person>();
Person person1 = new Person
{
Name = "John",
Age = 18
};
set.Add(person1);
Person person2 = new Person
{
Name = "John",
Age = 18
};
Console.WriteLine(set.Contains(person2));
如果 Person
是第三方类库提供的,我们无法覆写它的 Equals()
方法和 GetHashCode()
方法,这时候我们可以自定义一个类,实现 IEqualityComparer<T>
接口:
class PersonEqualityComParer : IEqualityComparer<Person>
{
public bool Equals(Person? x, Person? y)
{
if (x is null || y is null)
{
return false;
}
if (x.Name != y.Name)
{
return false;
}
if (x.Age != y.Age)
{
return false;
}
return true;
}
public int GetHashCode([DisallowNull] Person obj)
{
int hash = 17;
hash = hash * 31 + obj.Name.GetHashCode();
hash = hash * 31 + obj.Age.GetHashCode();
return hash;
}
}
将 PersonEqualityComParer
实例传入 HashSet 的构造函数,我们便可以自定义哈希表的匹配方式,如下代码将输出 True:
HashSet<Person> set = new HashSet<Person>(new PersonEqualityComParer());
Person person1 = new Person
{
Name = "John",
Age = 18
};
set.Add(person1);
Person person2 = new Person
{
Name = "John",
Age = 18
};
Console.WriteLine(set.Contains(person2));
前面讲的一大堆都是关于 IEqualityComparer<T>
接口的,那 EqualityComparer<T>
又是做什么的呢?这就不得不提到非泛型版本的 IEqualityComparer
接口。如果你观察过 HashTable
集合类型(它是非泛型的),会发现它的构造器会可以接受 IEqualityComparer
接口实例:
public Hashtable(IEqualityComparer)
IEqualityComparer
接口的定义和 IEqualityComparer<T>
高度相似:
public interface IEqualityComparer
{
new bool Equals(object x, object y);
int GetHashCode(object obj);
}
如果我们想让前面实现的 PersonEqualityComParer
同时可用于泛型、非泛型哈希表,这两个接口都需要实现,显然很麻烦。为此 C# 提供了 EqualityComparer<T>
抽象类,比较器只需实现一次 Equals()
方法、一次 GetHashCode()
方法便可用于泛型、非泛型两种情况:
// 可同时用于泛型、非泛型哈希表:
HashSet<Person> set = new HashSet<Person>(new PersonEqualityComParer());
Hashtable table = new Hashtable(new PersonEqualityComParer());
class PersonEqualityComParer : EqualityComparer<Person>
{
public override bool Equals(Person? x, Person? y)
{
if (x is null || y is null)
{
return false;
}
if (x.Name != y.Name)
{
return false;
}
if (x.Age != y.Age)
{
return false;
}
return true;
}
public override int GetHashCode([DisallowNull] Person obj)
{
int hash = 17;
hash = hash * 31 + obj.Name.GetHashCode();
hash = hash * 31 + obj.Age.GetHashCode();
return hash;
}
}
8. 一些特殊的预定义类型的相等比较
8.1 元组(ValueTuple
)
元组使用 Equals()
方法和使用 ==
运算符比较并不相同。以 ValueTuple<T1, T2>
为例,查看它的源码可以发现,它的 Equals()
方法通过调用 EqualityComparer<T>.Default.Equals()
实现相等比较:
public bool Equals(ValueTuple<T1, T2> other)
{
return EqualityComparer<T1>.Default.Equals(Item1, other.Item1)
&& EqualityComparer<T2>.Default.Equals(Item2, other.Item2);
}
如果你在源码中找不到 ==
运算符的重载!但是自 C#7.3 之后,又能使用 ==
、!=
运算符进行相等判断。
这是因为编译器就为元组类型提供了元组 ==
和 !=
的实现。编译器将 ==
运算符扩展到元素级别的 ==
操作。它会对每一对元素值执行 ==
操作(!=
运算符同理)。代码示例如下:
var t1 = (x: "x", y: "y", z: 1); // 比较时不考虑
var t2 = ("x", "y", 1); // 元素名称不同
Console.WriteLine(t1 == t2);
Console.WriteLine(t1.Item1 == t2.Item1 && //
t1.Item2 == t2.Item2 && // 编译器生成的
t1.Item3 == t2.Item3); // 等价代码
Console.WriteLine(t1 != t2);
Console.WriteLine(t1.Item1 != t2.Item1 || //
t1.Item2 != t2.Item2 || // 编译器生成的
t1.Item3 != t2.Item3); // 等价代码
这也要求元组中的类型必须能通过 ==
、!=
进行比较,以如下代码为例,因 Person 结构体未重载 ==
、!=
运算符,如下代码编译器报错 CS0019:
var tuple1 = (1, p1);
var tuple2 = (1, p2);
Console.WriteLine(tuple1 == tuple2);
struct Person
{
public int Age;
public string Name;
}
8.2 匿名类型
匿名类型本质上是引用类型,因此它可以使用 ==
运算符进行比较,它比较的是引用是否相等。它的 Equals()
方法则会比较所有元素是否相同(通过 EqualityComparer<T>.Default.Equals()
方法),考虑到它常用于 LINQ,Equals()
比较所有元素是否相同的行为就非常合理了。
两个匿名类型实例相同的前提是:类型相同、属性名称相同、属性顺序相同。以如下代码为例,它将输出 True、False、False、False
var value1 = new { Name = "John", Age = 18 };
var value2 = new { Name = "John", Age = 18 };
var value3 = new { Age = 18, Name = "John" };
var value4 = new { Title = "John", Level = 18 };
Console.WriteLine(value1.Equals(value2));
Console.WriteLine(value1.Equals(value3));
Console.WriteLine(value1.Equals(value4));
Console.WriteLine(value1 == value2);
Tips
为什么说“考虑到匿名类型常用于 LINQ,
Equals()
比较所有元素是否相同的行为就非常合理了”?以如下代码为例,我们通过匿名类型在一个查询中基于多个键进行连接查询,这就用到了匿名类型的
Equals()
方法:from s in stringProps
join b in builderProps on new { s.Name, s.PropertyType }
equals new { b.Name, b.PropertyType }
9. 内部元素结构化相等比较
有些类型的数据我们需要对内部元素进行相等比较,如数组、元组。此时可用通过 IStructuralEquatable
接口的 Equals()
方法执行该操作。数组和元组实现了该接口。下面是两个简单用例,分别演示了对数组、元组内部元素的相等比较:
int[] nums1 = [1, 2, 3, 4, 5];
int[] nums2 = [1, 2, 3, 4, 5];
Console.WriteLine(nums1.Equals(nums2));
IStructuralEquatable se = (IStructuralEquatable)nums1;
Console.WriteLine(se.Equals(nums2, EqualityComparer<int>.Default));
var t1 = (1, "foo");
var t2 = (1, "FOO");
IStructuralEquatable se1 = t1;
Console.WriteLine(se1.Equals(t2, StringComparer.InvariantCultureIgnoreCase));
Info
更多内容,另见第7章 集合 - hihaojie - 博客园 7.7.4 IStructuralEquatable 和 IStructualComparable
参考文献:
- 《框架设计指南:构建可复用.NET库的约定、惯例与模式》第三版
- 《C#7.0 核心技术指南》
- 《深入解析C#》第四版
Info
前两本书的部分内容,可参阅我的阅读笔记阅读笔记目录汇总 - hihaojie - 博客园
C# 中的“相等判断”的更多相关文章
- android应用中增加权限判断
android6.0系统允许用户管理应用权限,可以关闭/打开权限. 所以需要在APP中增加权限判断,以免用户关闭相应权限后,APP运行异常. 以MMS为例,在系统设置——应用——MMS——权限——&g ...
- sql 语句中使用条件判断case then else end
sql 语句中使用条件判断case then else end范例: SELECT les.[nLessonNo] FROM BS_Lesson AS les WHERE les.[sClassCod ...
- JAVA 中两种判断输入的是否是数字的方法__正则化_
JAVA 中两种判断输入的是否是数字的方法 package t0806; import java.io.*; import java.util.regex.*; public class zhengz ...
- JavaScript 中 if 条件判断
在JS中,If 除了能够判断bool的真假外,还能够判断一个变量是否有值. 下面的例子说明了JS中If的判断逻辑: 变量值 true '1' 1 '0' 'null' 2 '2' false 0 n ...
- Java中的空值判断
Java中的空值判断 /** * 答案选项: * A YouHaidong * B 空 * C 编译错误 * D 以上都不对 */ package com.you.model; /** * @auth ...
- Yaml 文件中Condition If- else 判断的问题
在做项目的CI/ CD 时,难免会用到 Travis.CI 和 AppVeyor 以及 CodeCov 来判断测试的覆盖率,今天突然遇到了一个问题,就是我需要在每次做测试的时候判断是否存在一个环境变量 ...
- tips:Java中while的判断条件
tips:Java中while的判断条件! 在c++中,有时候会遇到这种情况: while(x = y){ dosomething; } 如果x与y相等,这个时候如果循环体中没有跳出的点,那么会无限循 ...
- JS中,如何判断一个被转换的数是否是NaN
var x="abc"; //isNaN()函数判断是否是NaN if (isNaN(parseInt(x))) { alert("非数字"); } else{ ...
- shell中的条件判断以及与python中的对比
shell中比如比较字符串.判断文件是否存在及是否可读等,通常用"[]"来表示条件测试. 注意:这里的空格很重要.要确保方括号的空格. if ....; then ...
- 关于mybatis mapper.xml中的if判断
场景: 页面上有搜索框进行调节查询,不同搜索框中的内容可以为空. 过程: 点击搜索,前端把参数传给后台,这是后台要把为空的参数过滤掉. 做法: 通常我们在dao层即mapper.xml中进行过滤判断操 ...
随机推荐
- 【前端】【样式】CSS居中的三种方式
@charset "utf-8"; /* CSS Document */ /** *开发者:萌狼蓝天 *当前版本:v0.1[Debug] *最后更新日期:20210918 **/ ...
- Qt音视频开发25-ffmpeg音量设置
一.前言 音视频的播放.关闭.暂停.继续这几个基本功能,绝大部分人都是信手拈来的搞定,关于音量调节还是稍微饶了下弯弯,最开始打算采用各个系统的api来处理,坐下来发现不大好,系统的支持不完美,比如有些 ...
- [转]CMake与Make最简单直接的区别
写程序大体步骤为: 1.用编辑器编写源代码,如.c文件. 2.用编译器编译代码生成目标文件,如.o. 3.用链接器连接目标代码生成可执行文件,如.exe. 但如果源文件太多,一个一个编译时就会特别麻烦 ...
- [转]快速搭建简单的LBS程序——地图服务
很多时候,我们的程序需要提供需要搭建基于位置的服务(LBS),本文这里简单的介绍一下其涉及的一些基本知识. 墨卡托投影 地图本身是一个三维图像,但在电脑上展示时,往往需要将其转换为二维的平面图形,需要 ...
- OpenMMLab AI实战营 第二课笔记 计算机视觉之图像分类算法基础
OpenMMLab AI实战营 第二课笔记 目录 OpenMMLab AI实战营 第二课笔记 图像分类与基础视觉基础 1.图像分类问题 1.1 问题的数学表示 1.2 视觉任务的难点 1.2.1 超越 ...
- C# Unit test TestInitialize\TestCleanp
C# TestInitialize\TestCleanp ※※冰馨※※ 2020-12-15 09:19:37 75 收藏分类专栏: VS版权 VS专栏收录该内容197 篇文章1 订阅订阅专栏带有[C ...
- Restful、SOAP、RPC、SOA、微服务之间的区别-copy
什么是Restful Restful是一种架构设计风格,提供了设计原则和约束条件,而不是架构,而满足这些约束条件和原则的应用程序或设计就是 Restful架构或服务. 主要的设计原则: 资源与URI ...
- 防止SQL注入的五种方法
1.首先看一下下面两个sql语句的区别: <select id="selectByNameAndPassword" parameterType="java.util ...
- 分布式多级缓存(本地缓存,redis缓存)
结构包: 使用案例: 实现效果: 1.基本并发的本地缓存,基于分布式轻量级锁的redis缓存 2.热缓存(高频访问持续缓存)+快速过期(本地缓存2秒,redis缓存10秒) 3.方法级别缓存清理 (@ ...
- oracle 根据节点id递归查询全部的父节点(转载)
本文转载自 https://blog.csdn.net/BondChenJ/article/details/78581625 1.适用状况:blog 适用树状结构数据,例如包含id,parent_ ...