ZetCode

C# 运算符

最后修改于 2023 年 7 月 5 日

在本文中,我们将介绍 C# 运算符。

表达式由操作数和运算符构成。表达式的运算符指示要对操作数应用哪些操作。表达式中运算符的求值顺序由运算符的优先级结合性决定。

运算符是一个特殊的符号,它表示执行某个特定的过程。编程语言中的运算符来自数学。程序员使用数据。运算符用于处理数据。操作数是运算符的输入(参数)之一。

C# 运算符列表

下表显示了 C# 语言中使用的一组运算符。

类别 符号
符号运算符 + -
算术 + - * / %
逻辑(布尔和按位) & | ^ ! ~ && || true false
字符串连接 +
递增、递减 ++ --
移位 << >>
关系 == != < > <= >=
赋值 = += -= *= /= %= &= |= ^= ??= <<= >>=
成员访问 . ?.
索引 [] ?[]
转换
三元 ?:
委托串联和删除 + -
对象创建 new
类型信息 as is sizeof typeof
溢出异常控制 checked unchecked
间接寻址和地址 * -> [] &
Lambda =>

一个运算符通常有一个或两个操作数。那些只使用一个操作数的运算符称为一元运算符。那些使用两个操作数的运算符称为二元运算符。还有一个三元运算符 ?:,它使用三个操作数。

某些运算符可以在不同的上下文中使用。例如,+ 运算符。从上表可以看出,它用于不同的情况。它添加数字、连接字符串或委托;指示数字的符号。我们说运算符被重载了。

C# 一元运算符

C# 一元运算符包括:+、-、++、--、转换运算符 () 和非运算 !。

C# 符号运算符

有两个符号运算符:+-。它们用于表示或改变值的符号。

Program.cs
Console.WriteLine(2);
Console.WriteLine(+2);
Console.WriteLine(-2);

+- 符号表示值的符号。加号可用于指示我们有一个正数。它可以省略,并且通常这样做。

Program.cs
int a = 1;
Console.WriteLine(-a);
Console.WriteLine(-(-a));

减号会改变值的符号。

$ dotnet run
-1
1

C# 递增和递减运算符

将值加 1 或减 1 是编程中的常见任务。C# 有两个方便的运算符用于此操作:++--

x++;
x = x + 1;
...
y--;
y = y - 1;

以上两对表达式执行相同的操作。

Program.cs
int x = 6;

x++;
x++;

Console.WriteLine(x);

x--;
Console.WriteLine(x);

在上面的示例中,我们演示了这两个运算符的用法。

int x = 6;

x++;
x++;

我们将 x 变量初始化为 6。然后我们对 x 加一两次。现在变量的值是 8。

x--;

我们使用了递减运算符。现在变量的值是 7。

$ dotnet run
8
7

C# 显式转换运算符

显式转换运算符 () 可用于将一种类型转换为另一种类型。请注意,此运算符仅适用于某些类型。

Program.cs
float val = 3.2f;
int num = (int) val;

Console.WriteLine(num);

在本例中,我们显式地将 float 类型转换为 int

非运算符

非运算符 (!) 反转其操作数的含义。

Program.cs
var isValid = false;

if (!isValid)
{
    Console.WriteLine("The option is not valid");
}

在本例中,我们构建一个否定条件:如果表达式的逆是有效的,则执行该条件。

C# 赋值运算符

赋值运算符 = 将一个值赋给一个变量。变量是值的占位符。在数学中,= 运算符具有不同的含义。在方程中,= 运算符是等式运算符。等式的左边等于右边。

int x = 1;

在这里,我们将一个数字赋给 x 变量。

x = x + 1;

前面的表达式在数学上没有意义。但它在编程中是合法的。该表达式将 1 加到 x 变量。右边等于 2,并将 2 赋给 x

3 = x;

此代码示例会导致语法错误。我们不能将值赋给字面量。

C# 连接字符串

+ 运算符也用于连接字符串。

Program.cs
Console.WriteLine("Return " + "of " + "the king.");

我们使用字符串连接运算符将三个字符串连接在一起。

$ dotnet run
Return of the king.

C# 算术运算符

以下是 C# 中算术运算符的表格。

符号名称
+加法
-减法
*乘法
/除法
%余数

下面的示例展示了算术运算。

Project.cs
int a = 10;
int b = 11;
int c = 12;

int add = a + b + c;
int sb = c - a;
int mult = a * b;
int div = c / 3;
int rem = c % a;

Console.WriteLine(add);
Console.WriteLine(sb);
Console.WriteLine(mult);
Console.WriteLine(div);
Console.WriteLine(rem);

在前面的示例中,我们使用了加法、减法、乘法、除法和取余运算。这些都与数学中的一样熟悉。

int rem = c % a;

% 运算符称为余数或模运算符。它用于找出两个数相除的余数。例如,9 % 4,9 模 4 等于 1,因为 4 能整除 9 两次,余数为 1。

$ dotnet run
33
2
110
4
2

接下来,我们展示整数和浮点数除法之间的区别。

Program.cs
int c = 5 / 2;
Console.WriteLine(c);

double d = 5 / 2.0;
Console.WriteLine(d);

在前面的示例中,我们对两个数进行了除法运算。

int c = 5 / 2;
Console.WriteLine(c);

在这段代码中,我们进行了整数除法。除法运算的返回值是整数。当我们对两个整数进行除法时,结果是一个整数。

double d = 5 / 2.0;
Console.WriteLine(d);

如果其中一个值是双精度浮点数或单精度浮点数,我们就执行浮点数除法。在本例中,第二个操作数是双精度浮点数,所以结果也是双精度浮点数。

$ dotnet run
2
2.5

C# 布尔运算符

在 C# 中,我们有三个逻辑运算符。bool 关键字用于声明布尔值。

符号名称
&&逻辑与
||逻辑或
!逻辑非

布尔运算符也称为逻辑运算符。

Program.cs
int x = 3;
int y = 8;

Console.WriteLine(x == y);
Console.WriteLine(y > x);

if (y > x)
{
    Console.WriteLine("y is greater than x");
}

许多表达式都产生布尔值。布尔值用于条件语句。

Console.WriteLine(x == y);
Console.WriteLine(y > x);

关系运算符始终产生布尔值。这两行打印 false 和 true。

if (y > x)
{
    Console.WriteLine("y is greater than x");
}

只有当括号内的条件满足时,才会执行 if 语句的主体。y > x 返回 true,因此消息 "y is greater than x" 会打印到终端。

truefalse 关键字表示 C# 中的布尔字面量。

Program.cs
bool a = true && true;
bool b = true && false;
bool c = false && true;
bool d = false && false;

Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine(d);

示例显示了逻辑与运算符。仅当两个操作数都为 true 时,它才计算为 true。

$ dotnet run
True
False
False
False

只有一个表达式的结果为 True

如果任一操作数为 true,则逻辑或 || 运算符的计算结果为 true。

Program.cs
bool a = true || true;
bool b = true || false;
bool c = false || true;
bool d = false || false;

Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine(d);

如果运算符的任一边为 true,则操作的结果为 true。

$ dotnet run
True
True
True
False

四分之三的表达式结果为 true。

否定运算符 ! 将 true 变为 false,将 false 变为 true。

Program.cs
Console.WriteLine(!true);
Console.WriteLine(!false);
Console.WriteLine(!(4 < 3));

该示例展示了否定运算符的实际应用。

$ dotnet run
False
True
True

||&& 运算符是短路求值的。短路求值意味着只有当第一个参数不足以确定表达式的值时,才对第二个参数进行求值:当逻辑与的第一个参数计算结果为 false 时,总值必须为 false;当逻辑或的第一个参数计算结果为 true 时,总值必须为 true。短路求值主要用于提高性能。

一个例子可以更清楚地说明这一点。

Program.cs
Console.WriteLine("Short circuit");

if (One() && Two())
{
    Console.WriteLine("Pass");
}

Console.WriteLine("#############");

if (Two() || One())
{
    Console.WriteLine("Pass");
}

bool One()
{
    Console.WriteLine("Inside one");

    return false;
}

bool Two()
{
    Console.WriteLine("Inside two");

    return true;
}

我们在示例中有两种方法。它们用作布尔表达式中的操作数。

if (One() && Two())
{
    Console.WriteLine("Pass");
}

One 方法返回 false。短路 && 不会评估第二种方法。没有必要。一旦一个操作数是 false,逻辑结论的结果就永远是 false。只有“Inside one”才会被打印到控制台。

Console.WriteLine("#############");

if (Two() || One())
{
    Console.WriteLine("Pass");
}

在第二种情况下,我们使用 || 运算符并将 Two 方法用作第一个操作数。在这种情况下,字符串“Inside two”和“Pass”将打印到终端。同样,没有必要评估第二个操作数,因为一旦第一个操作数评估为 true,逻辑或就永远是 true

$ dotnet run
Short circuit
Inside one
#############
Inside two
Pass

C# 关系运算符

关系运算符用于比较值。这些运算符始终产生布尔值。

符号含义
<小于
<=小于或等于
>大于
>=大于或等于
==等于
!=不等于

关系运算符也称为比较运算符。

Program.cs
Console.WriteLine(3 < 4);
Console.WriteLine(3 == 4);
Console.WriteLine(4 >= 3);
Console.WriteLine(4 != 3);

在代码示例中,我们有四个表达式。这些表达式比较整数值。每个表达式的结果要么是 true,要么是 false。在 C# 中,我们使用 == 来比较数字。一些语言(如 Ada、Visual Basic 或 Pascal)使用 = 来比较数字。

C# 位运算符

十进制数对人类来说很自然。二进制数对计算机来说是原生的。二进制、八进制、十进制或十六进制符号只是相同数字的表示法。位运算符处理二进制数的位。位运算符很少用于高级语言(如 C#)。

符号含义
~按位取反
^按位异或
&按位与
|按位或

按位取反运算符 将每个 1 变为 0,将 0 变为 1。

Console.WriteLine(~ 7); // prints -8
Console.WriteLine(~ -8); // prints 7

该运算符会翻转数字 7 的所有位。其中一位也决定了该数字是否为负数。如果我们再次对所有位进行取反,我们会得到数字 7。

按位与运算符对两个数字执行逐位比较。只有当操作数中对应的位都为 1 时,结果的该位才为 1。

      00110
   &  00011
   =  00010

第一个数字是 6 的二进制表示,第二个是 3,结果是 2。

Console.WriteLine(6 & 3); // prints 2
Console.WriteLine(3 & 6); // prints 2

按位或运算符对两个数字执行逐位比较。当操作数中对应的位任一为 1 时,结果的该位为 1。

     00110
   | 00011
   = 00111

结果是 00110 或十进制的 7。

Console.WriteLine(6 | 3); // prints 7
Console.WriteLine(3 | 6); // prints 7

按位异或运算符对两个数字执行逐位比较。当操作数中对应的位任一为 1 但不是两者都为 1 时,结果的该位为 1。

      00110
   ^  00011
   =  00101

结果是 00101 或十进制的 5。

Console.WriteLine(6 ^ 3); // prints 5
Console.WriteLine(3 ^ 6); // prints 5

C# 复合赋值运算符

复合赋值运算符由两个运算符组成。它们是简写运算符。

a = a + 3;
a += 3;

+= 复合运算符是这些简写运算符之一。上面的两个表达式是相等的。将值 3 加到 a 变量上。

其他复合运算符是

-=   *=   /=   %=   &=   |=   <<=   >>=
Program.cs
int a = 1;
a = a + 1;

Console.WriteLine(a);

a += 5;
Console.WriteLine(a);

a *= 3;
Console.WriteLine(a);

在本例中,我们使用两个复合运算符。

int a = 1;
a = a + 1;

a 变量初始化为 1。使用非速记符号将 1 加到变量中。

a += 5;

使用 += 复合运算符,我们将 5 加到 a 变量。该语句等价于 a = a + 5;

a *= 3;

使用 *= 运算符,a 乘以 3。该语句等价于 a = a * 3;

$ dotnet run
2
7
21

C# new 运算符

new 运算符用于创建对象和调用构造函数。

Program.cs
var b = new Being();
Console.WriteLine(b);

var vals = new int[] { 1, 2, 3, 4, 5 };
Console.WriteLine(string.Join(" ", vals));

class Being
{
    public Being()
    {
        Console.WriteLine("Being created");
    }
}

在本例中,我们使用 new 运算符创建一个新的自定义对象和一个整数数组。

public Being()
{
    Console.WriteLine("Being created");
}

这是一个构造函数。它在对象创建时被调用。

$ dotnet run
Being created
Being
1 2 3 4 5

C# 访问运算符

访问运算符 [] 与数组、索引器和属性一起使用。

Program.cs
var vals = new int[] { 2, 4, 6, 8, 10 };
Console.WriteLine(vals[0]);

var domains = new Dictionary()
{
    { "de", "Germany" },
    { "sk", "Slovakia" },
    { "ru", "Russia" }
};

Console.WriteLine(domains["de"]);

oldMethod();

[Obsolete("Don't use OldMethod, use NewMethod instead", false)]
void oldMethod()
{
    Console.WriteLine("oldMethod()");
}

void newMethod()
{
    Console.WriteLine("newMethod()");
}

在本例中,我们使用 [] 运算符获取数组的元素、字典对的值并激活内置属性。

var vals = new int[] { 2, 4, 6, 8, 10 };
Console.WriteLine(vals[0]);

我们定义一个整数数组。我们使用 vals[0] 获取第一个元素。

var domains = new Dictionary<string, string>()
{
    { "de", "Germany" },
    { "sk", "Slovakia" },
    { "ru", "Russia" }
};

Console.WriteLine(domains["de"]);

创建一个字典。使用 domains["de"],我们获取具有 "de" 键的对的值。

[Obsolete("Don't use OldMethod, use NewMethod instead", false)]
public static void oldMethod()
{
    Console.WriteLine("oldMethod()");
}

我们激活内置的 Obsolete 属性。该属性发出警告。

当我们运行程序时,它会生成警告:warning CS0618: 'oldMethod()' is obsolete: 'Don't use OldMethod, use NewMethod instead'

C# 从末尾索引运算符 ^

从末尾索引运算符 ^ 指示序列末尾的元素位置。例如,^1 指向序列的最后一个元素,而 ^n 指向偏移量为 length - n 的元素。

Program.cs
int[] vals = { 1, 2, 3, 4, 5 };

Console.WriteLine(vals[^1]);
Console.WriteLine(vals[^2]);

var word = "gray falcon";

Console.WriteLine(word[^1]);

在本例中,我们将运算符应用于数组和字符串。

int[] vals = { 1, 2, 3, 4, 5 };

Console.WriteLine(vals[^1]);
Console.WriteLine(vals[^2]);

我们打印数组的最后一个元素和倒数第二个元素。

var word = "gray falcon";

Console.WriteLine(word[^1]);

我们打印该单词的最后一个字母。

$ dotnet run
5
4
n

C# 范围运算符 ..

.. 运算符指定其操作数的索引范围的开始和结束。左侧操作数是范围的包含起始值。右侧操作数是范围的独占结束值。

x.. is equivalent to x..^0
..y is equivalent to 0..y
.. is equivalent to 0..^0

可以省略 .. 运算符的操作数以获得开放范围。

Program.cs
int[] vals = { 1, 2, 3, 4, 5, 6, 7 };

var slice1 = vals[1..4];
Console.WriteLine("[{0}]", string.Join(", ", slice1));

var slice2 = vals[..^0];
Console.WriteLine("[{0}]", string.Join(", ", slice2));

在本例中,我们使用 .. 运算符获取数组切片。

var range1 = vals[1..4];
Console.WriteLine("[{0}]", string.Join(", ", range1));

我们从索引 1 到索引 4 创建一个数组切片;最后一个索引 4 不包括在内。

var slice2 = vals[..^0];
Console.WriteLine("[{0}]", string.Join(", ", slice2));

在这里,我们本质上创建了数组的副本。

$ dotnet run
[2, 3, 4]
[1, 2, 3, 4, 5, 6, 7]

C# 类型信息

现在我们关注处理类型的运算符。

sizeof 运算符用于获取值类型的大小(以字节为单位)。typeof 用于获取类型的 System.Type 对象。

Program.cs
Console.WriteLine(sizeof(int));
Console.WriteLine(sizeof(float));
Console.WriteLine(sizeof(Int32));

Console.WriteLine(typeof(int));
Console.WriteLine(typeof(float));

我们使用 sizeoftypeof 运算符。

$ dotnet run
4
4
4
System.Int32
System.Single

我们可以看到 int 类型是 System.Int32 的别名,而 floatSystem.Single 类型的别名。

is 运算符检查对象是否与给定类型兼容。

Program.cs
Base _base = new Base();
Derived derived = new Derived();

Console.WriteLine(_base is Base);
Console.WriteLine(_base is Object);
Console.WriteLine(derived is Base);
Console.WriteLine(_base is Derived);

class Base { }
class Derived : Base { }

我们从用户定义的类型创建两个对象。

class Base {}
class Derived : Base {}

我们有一个 Base 类和一个 Derived 类。Derived 类继承自 Base 类。

Console.WriteLine(_base is Base);
Console.WriteLine(_base is Object);

Base 等于 Base,因此第一行打印 True。Base 也与 Object 类型兼容。这是因为每个类都继承自所有类的父类 — Object 类。

Console.WriteLine(derived is Base);
Console.WriteLine(_base is Derived);

派生对象与 Base 类兼容,因为它显式地继承自 Base 类。另一方面,_base 对象与 Derived 类无关。

$ dotnet run
True
True
True
False

as 运算符用于在兼容的引用类型之间执行转换。当转换不可能时,运算符返回 null。与引发异常的强制转换操作不同。

Program.cs
object[] objects = new object[6];
objects[0] = new Base();
objects[1] = new Derived();
objects[2] = "ZetCode";
objects[3] = 12;
objects[4] = 1.4;
objects[5] = null;

for (int i = 0; i < objects.Length; i++)
{
    string s = objects[i] as string;
    Console.Write("{0}:", i);

    if (s != null)
    {
        Console.WriteLine(s);
    }
    else
    {
        Console.WriteLine("not a string");
    }
}

class Base { }
class Derived : Base { }

在上面的示例中,我们使用 as 运算符执行强制转换。

string s = objects[i] as string;

我们尝试将各种类型转换为字符串类型。但是只有在转换有效时才进行转换。

$ dotnet run
0:not a string
1:not a string
2:ZetCode
3:not a string
4:not a string
5:not a string

C# 运算符优先级

运算符优先级告诉我们哪些运算符首先被求值。优先级级别对于避免表达式中的歧义是必需的。

以下表达式的结果是 28 还是 40?

3 + 5 * 5

就像在数学中一样,乘法运算符的优先级高于加法运算符。所以结果是 28。

(3 + 5) * 5

要更改求值顺序,我们可以使用括号。括号内的表达式始终首先被求值。

下表显示了按优先级排序的常用 C# 运算符(优先级最高在前)

运算符 类别 结合性
主要 x.y x?.y, x?[y] f(x) a[x] x++ x-- new typeof default checked unchecked
一元 + - ! ~ ++x --x (T)x
乘法 * / %
加法 + -
移位 << >>
相等 == !=
逻辑与 &
逻辑异或 ^
逻辑或 |
条件与 &&
条件或 ||
空合并 ??
三元 ?:
赋值 = *= /= %= += -= <<= >>= &= ^= |= ??= =>

表格同一行上的运算符具有相同的优先级。

Program.cs
Console.WriteLine(3 + 5 * 5);
Console.WriteLine((3 + 5) * 5);

Console.WriteLine(! true | true);
Console.WriteLine(! (true | true));

在这段代码示例中,我们展示了几个表达式。每个表达式的结果取决于优先级级别。

Console.WriteLine(3 + 5 * 5);

此行打印 28。 乘法运算符的优先级高于加法。 首先,计算 5*5 的乘积,然后加 3。

Console.WriteLine(! true | true);

在这种情况下,否定运算符具有更高的优先级。首先,第一个真值被否定为假,然后 | 运算符组合假和真,最终得到真。

$ dotnet run
28
40
True
False

C# 关联规则

有时,优先级不足以确定表达式的结果。还有另一个称为关联性的规则。运算符的关联性确定具有相同优先级的运算符的求值顺序。

9 / 3 * 3

此表达式的结果是什么,9 还是 1?乘法、除法和模运算符都是从左到右关联的。因此,表达式的求值方式如下:(9 / 3) * 3,结果为 9。

算术、布尔、关系和位运算符都是从左到右关联的。

另一方面,赋值运算符是右结合的。

Program.cs
int a, b, c, d;
a = b = c = d = 0;

Console.WriteLine("{0} {1} {2} {3}", a, b, c, d);

int j = 0;
j *= 3 + 1;

Console.WriteLine(j);

在本例中,我们有两种情况,其中关联规则决定了表达式。

int a, b, c, d;
a = b = c = d = 0;

赋值运算符是从右到左关联的。如果关联性是从左到右,则前面的表达式将不可行。

int j = 0;
j *= 3 + 1;

复合赋值运算符是从右到左关联的。我们可能期望结果为 1。但实际结果是 0。因为关联性。右边的表达式首先被求值,然后应用复合赋值运算符。

$ dotnet run
0 0 0 0
0

C# 空条件运算符

空条件运算符仅在其操作数计算结果为非空时,才对其操作数应用成员访问 ?. 或元素访问 ?[] 操作。如果操作数的计算结果为 null,则应用运算符的结果为 null。

Program.cs
var users = new List<User>() { new User("John Doe", "gardener"), new User(null, null),
        new User("Lucia Newton", "teacher") };

users.ForEach(user => Console.WriteLine(user.Name?.ToUpper()));

record User(string? Name, string? Occupation);

在本例中,我们有一个 User 类,其中有两个成员:NameOccupation。我们借助 ?. 运算符访问对象的 name 成员。

var users = new List<User>() { new User("John Doe", "gardener"), new User(null, null),
        new User("Lucia Newton", "teacher") };

我们有一个用户列表。其中一个用户使用空值初始化。

users.ForEach(user => Console.WriteLine(user.Name?.ToUpper()));

我们使用 ?. 访问 Name 成员并调用 ToUpper 方法。?. 通过不调用 null 值的 ToUpper 来防止 System.NullReferenceException

$ dotnet run
JOHN DOE

LUCIA NEWTON

在以下示例中,我们使用 ?[] 运算符。该运算符允许将 null 值放入集合中。

Program.cs
int?[] vals = { 1, 2, 3, null, 4, 5 };

int i = 0;

while (i < vals.Length)
{
    Console.WriteLine(vals[i]?.GetType());
    i++;
}

在本例中,我们在一个数组中有一个 null 值。我们通过将 ?. 运算符应用于数组元素来防止 System.NullReferenceException

C# 空合并运算符

空合并运算符 ?? 用于为 可空 类型定义默认值。如果左侧操作数不为 null,则返回它;否则返回右侧操作数。当我们处理数据库时,我们经常处理不存在的值。这些值作为空值进入程序。此运算符是处理此类情况的便捷方法。

Program.cs
int? x = null;
int? y = null;

int z = x ?? y ?? -1;

Console.WriteLine(z);

空合并运算符的示例程序。

int? x = null;
int? y = null;

两个可空 int 类型被初始化为 nullint?Nullable<int> 的简写形式。它允许将空值分配给 int 类型。

int z = x ?? y ?? -1;

我们希望为 z 变量分配一个值。但它一定不能为 null。这是我们的要求。我们可以很容易地使用空合并运算符来实现这一点。如果 xy 变量都为 null,则将 -1 赋给 z

$ dotnet run
-1

C# 空合并赋值运算符

空合并赋值运算符 ??= 仅在其左侧操作数的计算结果为 null 时,才将其右侧操作数的值赋给其左侧操作数。如果左侧操作数的计算结果为非空,则 ??= 运算符不计算其右侧操作数。它在 C# 8.0 及更高版本中可用。

Program.cs
List<int> vals = null;

vals ??= new List<int>() {1, 2, 3, 4, 5, 6};
vals.Add(7);
vals.Add(8);
vals.Add(9);

Console.WriteLine(string.Join(", ", vals));

vals ??= new List<int>() {1, 2, 3, 4, 5, 6};

Console.WriteLine(string.Join(", ", vals));

在本例中,我们在一个整数值列表上使用空合并赋值运算符。

List<int> vals = null;

首先,将列表赋给 null

vals ??= new List<int>() {1, 2, 3, 4, 5, 6};

我们使用 ??= 将一个新列表对象分配给变量。由于它是 null,因此分配了该列表。

vals.Add(7);
vals.Add(8);
vals.Add(9);

Console.WriteLine(string.Join(", ", vals));

我们将一些值添加到列表中并打印其内容。

vals ??= new List<int>() {1, 2, 3, 4, 5, 6};

我们尝试将一个新的列表对象分配给变量。由于变量不再是 null,因此未分配该列表。

$ dotnet run
1, 2, 3, 4, 5, 6, 7, 8, 9
1, 2, 3, 4, 5, 6, 7, 8, 9

C# 三元运算符

三元运算符 ?: 是一个条件运算符。 对于我们想要根据条件表达式选择两个值之一的情况,它是一个方便的运算符。

cond-exp ? exp1 : exp2

如果 cond-exp 为 true,则评估 exp1 并返回结果。 如果 cond-exp 为 false,则评估 exp2 并返回其结果。

Program.cs
int age = 31;

bool adult = age >= 18 ? true : false;

Console.WriteLine("Adult: {0}", adult);

在大多数国家/地区,成年是根据您的年龄来决定的。如果您超过某个年龄,您就是成年人。这是三元运算符的情况。

bool adult = age >= 18 ? true : false;

首先,评估赋值运算符右侧的表达式。三元运算符的第一阶段是条件表达式求值。因此,如果年龄大于或等于18,则返回 ? 字符后面的值。如果不是,则返回 : 字符后面的值。然后,返回的值被分配给 adult 变量。

$ dotnet run
Adult: True

一个 31 岁的人是成年人。

C# Lambda 运算符

=> 标记被称为 lambda 运算符。它是一个取自函数式语言的运算符。该运算符可以使代码更短、更简洁。另一方面,理解该语法可能很棘手。特别是如果程序员以前从未使用过函数式语言。

任何可以使用委托的地方,我们也可以使用 lambda 表达式。lambda 表达式的定义是:lambda 表达式是一个匿名函数,可以包含表达式和语句。在左侧,我们有一组数据,在右侧,有一个表达式或一组语句。这些语句应用于数据的每个项目。

在 lambda 表达式中,我们没有 return 关键字。最后一个语句会自动返回。我们也不需要为我们的参数指定类型。编译器将推断出正确的参数类型。这被称为类型推断。

Program.cs
var list = new List<int>() { 3, 2, 1, 8, 6, 4, 7, 9, 5 };

var subList = list.FindAll(val => val > 3);

foreach (int i in subList)
{
    Console.WriteLine(i);
}

我们有一个整数列表。我们打印所有大于 3 的数字。

var list = new List<int>() { 3, 2, 1, 8, 6, 4, 7, 9, 5 };

我们有一个泛型整数列表。

var subList = list.FindAll(val => val > 3);

这里我们使用 lambda 运算符。FindAll 方法将一个谓词作为参数。谓词是一种特殊的委托,它返回一个布尔值。该谓词应用于列表的所有项目。val 是一个没有指定类型的输入参数。我们可以显式地指定类型,但没有必要。

编译器将期望一个 int 类型。val 是来自列表的当前输入值。它被比较,看是否大于 3,并返回一个布尔值 true 或 false。最后,FindAll 将返回满足条件的所有值。它们被分配给 sublist 集合。

foreach (int i in subList)
{
    Console.WriteLine(i);
}

sublist 集合的项目被打印到终端。

$ dotnet run
8
6
4
7
9
5

来自大于 3 的整数列表的值。

Program.cs
var nums = new List<int>() { 3, 2, 1, 8, 6, 4, 7, 9, 5 };

var nums2 = nums.FindAll( delegate(int i) {
        return i > 3;
    }
);

foreach (int i in nums2)
{
    Console.WriteLine(i);
}

这是同一个例子。我们使用一个匿名委托而不是 lambda 表达式。

C# 计算质数

我们将计算质数。

Program.cs
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 
    14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 };

Console.Write("Prime numbers: ");

foreach (int num in nums)
{
    if (num == 1) continue;

    if (num == 2 || num == 3)
    {
        Console.Write(num + " ");
        continue;
    }

    int i = (int) Math.Sqrt(num);
    bool isPrime = true;

    while (i > 1)
    {
        if (num % i == 0)
        {
            isPrime = false;
        }
        i--;
    }

    if (isPrime)
    {
        Console.Write(num + " ");
    }
}

Console.Write('\n');

在上面的例子中,我们处理了许多不同的运算符。质数(或素数)是一个自然数,它恰好有两个不同的自然数除数:1 和它本身。我们取一个数字,并将其除以数字,从 1 到所选数字。实际上,我们不必尝试所有较小的数字;我们可以除以小于所选数字的平方根的数字。该公式将起作用。我们使用取余运算符。

int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 
    14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 };

我们从这些数字中计算质数。

if (num == 1) continue;

根据定义,1 不是质数

if (num == 2 || num == 3)
{
    Console.Write(num + " ");
    continue;
}

我们跳过对 2 和 3 的计算:它们是质数。注意等号和条件或运算符的用法。== 的优先级高于 || 运算符。所以我们不需要使用括号。

int i = (int) Math.Sqrt(num);

如果只尝试小于所讨论数字的平方根的数字,我们就没问题。数学上已经证明,考虑小于所讨论数字的平方根的值就足够了。

while (i > 1)
{
    ...
    i--;
}

这是一个 while 循环。i 是所计算的数字的平方根。我们使用递减运算符将 i 每次循环周期减 1。当 i 小于 1 时,我们终止循环。例如,我们有数字 9。9 的平方根是 3。我们将 9 除以 3 和 2。

if (num % i == 0)
{
    isPrime = false;
}

这是算法的核心。如果取余运算符对任何 i 值返回 0,则所讨论的数字不是质数。

来源

C# 运算符和表达式

在本文中,我们介绍了 C# 运算符。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 C# 教程