注释
社区兴趣团体现已从 Yammer 迁移到Microsoft Viva Engage。 若要加入 Viva Engage 社区并参与最新讨论,请填写 “请求访问财务和运营 Viva Engage 社区 ”表单,然后选择要加入的社区。
本文比较了 X++ 和 C# 语法和编程。
X++,C# 比较:Hello World
本部分将最简单的 X++ 程序与 C# 中的对应程序进行比较。
X++ 到 C# 比较
以下部分介绍 X++ 和 C# 之间的一些基本相似之处和差异。
相似 之 处
对于 C#,以下 X++ 功能相同:
- 单行 (
//) 和多行 (/* */) 注释。 -
==(equal) 运算符,用于确定两个值是否相等。 -
!=(不等于)运算符,用于确定两个值是否不相等。 -
+字符串串联的 (加号) 运算符。
Differences
下表列出了 C# 中不同的 X++ 功能。
| 功能 / 特点 | X++ | C# | 注释 |
|---|---|---|---|
if 和 else 条件语句 |
该 if 语句接受它可以自动转换为布尔值的任何类型的表达式。 常见示例包括 int 0 表示 false,或 null 表示 false 的对象。 |
该 if 语句需要布尔表达式。 |
有关大括号和括号的语法结构在 X++ 和 C# 之间完全相同。 |
| 文本字符串 | 可以使用以下任一方法分隔文本字符串:
|
文本字符串必须由一对双引号(“)字符分隔。 | 对于 X++,双引号字符通常用于分隔字符串。 但是,当字符串必须包含双引号字符时,使用单引号字符分隔字符串是方便的。 |
煳 type |
X++ 中没有 char 字符类型。 可以声明长度为 str 1,但它仍然是一个字符串:str 1 myString = "a"; |
C# 中有一个 char 。 不能将参数作为参数传递给 char 输入 string 参数的方法,尽管可以首先显式转换为 char a string。 |
有关 X++ 数据类型的详细信息,请参阅基元数据类型。 |
| 消息输出 | X++ 在 Infolog 窗口中向用户传递消息。 常见方法包括:
|
对于命令行程序,可以将消息传送到控制台。 常见方法包括:
|
X++ 和 C# 示例
本部分包含两个简单的代码示例。 一个示例使用 X++ 编写,另一个示例采用 C# 编写。 这两个样本都取得了相同的结果。 演示了以下 X++ 功能:
-
//单行注释 -
/\*\*/多行注释 -
if语句 -
==运算符 -
!=运算符 -
+用于连接字符串的运算符 - 包含和不使用 Global:: 前缀的消息输出的 Global::info
- 消息输出的 Global::error
- 将单引号和双引号字符('和“)用作字符串分隔符。
注释
最佳做法是对可能向用户显示的任何字符串使用双引号。
X++ 示例
此 X++ 代码示例采用作业形式。 应用程序对象树(AOT)中有一个标题为“作业”的节点。 可以在“作业”节点下添加此示例,然后可以运行作业。
static void JobRs001a_HelloWorld(Args _args)
{
if (1 == 1)
{
// These two info() calls are identical to the X++ compiler.
// The second form is the one typically used in X++.
Global::info("Hello World, 1.");
info('Hello World, 2.');
}
if (1 != 1)
{
error("This message will not appear.");
}
else
{
// These two methods are also from the Global class.
// The + operator concatenates two strings.
warning("This is like info, but is for warnings, 3.");
error("This is like info, but is for errors, 4.");
}
}
输出
下面是 Infolog 窗口的输出:消息(09:49:48) Hello World,1。 Hello World, 2. 这类似于信息,但用于警告 3。 这类似于信息,但对于错误,4。
C# 示例
以下 C# 程序是对以前的 X++ 程序的重写。
using System;
class Pgm_CSharp
{
static void Main( string[] args )
{
new Pgm_CSharp().Rs001a_CSharp_HelloWorld();
}
void Rs001a_CSharp_HelloWorld()
{
if (1 == 1)
{
Console .Out .WriteLine("Hello World, Explicit .Out , 1.");
Console .WriteLine("Hello World, Implicit default to .Out , 2.");
}
if (1 != 1)
{
Console .Error .WriteLine("This message will not appear.");
}
else
{
Console .Error .WriteLine(".Error is like .Out, but can be for warnings, 3.");
Console .Error .WriteLine(".Error is like .Out, but is for errors, 4.");
}
}
}
输出
下面是 C# 控制台的实际输出:
Hello World, Explicit .Out, 1.
Hello World, Implicit default to .Out, 2.
.Error is like .Out, but can be for warnings, 3.
.Error is like .Out, but is for errors, 4.
X++、C# 比较:循环
本部分比较 X++ 和 C# 之间的循环功能。
相似 之 处
X++ 和 C# 中的以下功能相同:
- int 基元数据类型变量的声明。 其他基元类型的声明几乎相同,但类型可能具有不同的名称。
- while 语句 for 循环。
- 用于退出循环的 break 语句。
- continue 语句跳转到循环的顶部。
- <= (小于或等于)比较运算符。
Differences
下表列出了 C# 中不同的 X++ 功能。
| Features | X++ | C# | 注释 |
|---|---|---|---|
语句 for 。 |
for 语句可用于循环。 | C# for 语句与 for X++ 中略有不同。 |
在 C# 中,可以在语句中 for 声明计数器整数。 但在 X++ 中,计数器必须在语句外部 for 声明。 |
| ++ 递增运算符。 | X++ 中提供了一个 ++ 递增运算符。 但用 ++ 修饰的 int 变量只能用作语句,不能用作表达式。 例如,以下 X++ 代码行不会编译:int age=42;print age++;但是,以下 X++ 代码行将编译: int age=42;age++; print age; |
C# ++ 运算符比 X++ 更灵活。 | 这两种语言中的以下代码行相同:
|
| modulo 运算符。 | 在 X++ 中,modulo 运算符为 mod。 | 在 C# 中,模式运算符 %。 | 模态运算符的符号不同,但它们的行为在两种语言中都是相同的。 |
| 暂时挂起已经开始的控制台程序。 | 语句 pause 。 |
在 C# 中,命令行程序可以通过以下代码行暂停:Console.In.Read(); |
在 X++ 中,通过单击模式对话框中的“确定”按钮继续作。 在 C# 中,通过按键盘上的任何键盘继续作。 |
| 显示消息。 | 在 X++ 中,该 print 语句在“打印”窗口中显示一条消息。 |
在 C# 中,可以通过以下代码行在控制台上显示消息:Console.WriteLine(); |
仅当测试时,才使用 X++ print 函数。 几乎始终在 print 代码的某个位置使用 pause 语句的 X++ 程序。 对于生产 X++ 代码,请使用 Global::info 方法而不是 print. 函数 strfmt 通常与 info.. 之后没有理由使用pauseinfo。 |
| 发出声音。 | 蜂鸣函数发出可以听到的声音。 | 在 C# 中,可听到的声音由以下代码行发出:Console.Beep(); |
每个语句都生成一个简短的语气。 |
打印和全局::info
循环的 X++ 代码示例使用 print 函数显示结果。 在 X++ 中,可以使用 print 该语句可以显示任何基元数据类型,而无需先调用将其转换为字符串的函数。
print这在快速测试情况下非常有用。 通常,Global::info 方法的使用频率高于 print. 该方法 info 只能显示字符串。 因此,strfmt 函数通常与 info.. 其限制 print 是无法将“打印”窗口的内容复制到剪贴板(如 Ctrl+C)。 Global::info 写入到支持复制到剪贴板的 Infolog 窗口。
示例 1:while 循环
while 关键字支持在 X++ 和 C# 中循环。
的 X++ 示例
static void JobRs002a_LoopsWhile(Args _args)
{
int nLoops = 1;
while (nLoops <= 88)
{
print nLoops;
pause;
// The X++ modulo operator is mod.
if ((nLoops mod 4) == 0)
{
break;
}
++ nLoops;
}
beep(); // Function.
pause; // X++ keyword.
}
输出
X++ 打印窗口中的输出如下所示:
1
2
3
4
C# 的 while 示例
using System;
public class Pgm_CSharp
{
static void Main( string[] args )
{
new Pgm_CSharp().WhileLoops();
}
void WhileLoops()
{
int nLoops = 1;
while (nLoops <= 88)
{
Console.Out.WriteLine(nLoops.ToString());
Console.Out.WriteLine("(Press any key to resume.)");
// Paused until user presses a key.
Console.In.Read();
if ((nLoops % 4) == 0) {
break;
}
++ nLoops;
}
Console.Beep();
Console.In.Read();
}
}
输出
C# 程序的控制台输出如下所示:
1
(Press any key to resume.)
2
(Press any key to resume.)
3
(Press any key to resume.)
4
(Press any key to resume.)
示例 2:for 循环
for 关键字支持在 X++ 和 C# 中循环。
的 X++ 示例
在 X++ 中,计数器变量不能声明为 for 语句的一部分。
static void JobRs002a_LoopsWhileFor(Args _args)
{
int ii; // The counter.
for (ii=1; ii < 5; ii++)
{
print ii;
pause;
// You must click the OK button to proceed beyond a pause statement.
// ii is always less than 99.
if (ii < 99)
{
continue;
}
print "This message never appears.";
}
pause;
}
输出
X++ 打印窗口中的输出如下所示:
1
2
3
4
的 C# 示例
using System;
public class Pgm_CSharp
{
static void Main( string[] args )
{
new Pgm_CSharp().ForLoops();
}
void ForLoops()
{
int nLoops = 1, ii;
for (ii = 1; ii < 5; ii++)
{
Console.Out.WriteLine(ii.ToString());
Console.Out.WriteLine("(Press any key to resume.)");
Console.In.Read();
if (ii < 99)
{
continue;
}
Console.Out.WriteLine("This message never appears.");
}
Console.Out.WriteLine("(Press any key to resume.)");
Console.In.Read();
}
}
输出
C# 程序的控制台输出如下所示:
1
(Press any key to resume.)
2
(Press any key to resume.)
3
(Press any key to resume.)
4
(Press any key to resume.)
(Press any key to resume.)
X++,C# 比较:Switch
在 X++ 和 C# 中, switch 语句涉及关键字 大小写、 中断和 默认值。 下表列出了 X++ 和 C# 之间的 switch 语句之间的差异。
| 功能 / 特点 | X++ | C# | 注释 |
|---|---|---|---|
break; 在每个事例块的末尾 |
在 X++ 中,当任何 事例 块与 switch 子句上的表达式值匹配时,将执行所有其他 大小写 和 默认 块,直到 break; 达到语句。 X++ switch 语句中从未break;需要任何语句,但在break;几乎所有实际情况下,语句都很重要。 |
在 C# 中,在break;或默认块中的语句之后,始终需要语句。 如果 case 子句本身和下一个 case 子句之间没有语句,则两个 break; 子句之间不需要一个语句。 |
建议不要省略任何事例break;之后的语句,因为它可能会混淆编辑代码的下一个程序员。 |
break;
默认块的末尾 |
在 X++ 中,没有在break;块末尾添加语句的效果。 |
在 C# 中,编译器需要break;块末尾的语句。 |
有关详细信息,请参阅 Switch 语句。 |
| 仅 事例 块上的常量值 | 在 X++ 中,可以在事例块上指定文本值或变量。 例如,可以编写 case myInteger:。 | 在 C# 中,必须在每个 事例 块上指定一个文本值,并且不允许任何变量。 | 无注释。 |
| 一个 事例 块上的多个值 | 在 X++ 中,可以在每个事例块上指定多个值。 这些值必须用逗号分隔。 例如,可以编写 case 4,5,myInteger:。 |
在 C# 中,必须在每个 事例 块上指定一个值。 | 在 X++ 中,最好在一个事例块上编写多个值,而不是在一个或多个 事例 块的末尾省略 break; 语句。 |
开关的代码示例
以下各节显示了 X++ 和 C# 中的可比较开关语句。
X++ 开关示例
X++ 开关示例显示以下内容:
-
case iTemp:并case (93-90):显示 事例 表达式不限于常量,因为它们在 C# 中。 -
//break;如果显示break;X++ 中不需要这些语句,则表示它们几乎始终是可取的。 -
case 2, (93-90), 5:如果显示 X++ 中的一个 case 子句上可以列出多个表达式,
static void GXppSwitchJob21(Args _args) // X++ job in AOT > Jobs.
{
int iEnum = 3;
int iTemp = 6;
switch (iEnum)
{
case 1:
case iTemp: // 6
info(strFmt("iEnum is one of these values: 1,6: %1", iEnum));
break;
case 2, (93-90), str2Int("5"): // Equivalent to three 'case' clauses stacked, valid in X++.
//case 2:
//case (93-90): // Value after each 'case' can be a constant, variable, or expression; in X++.
//case str2Int("5"):
info(strFmt("iEnum is one of these values: 2,3,5: %1", iEnum));
//break; // Not required in X++, but usually wanted.
case 4:
info(strFmt("iEnum is one of these values: 4: %1", iEnum));
break;
default:
info(strFmt("iEnum is an unforeseen value: %1", iEnum));
break;
// None of these 'break' occurrences in this example are required for X++ compiler.
}
return;
}
/*** Copied from the Infolog:
Message (02:32:08 pm)
iEnum is one of these values: 2,3,5: 3
iEnum is one of these values: 4: 3
***
C# 开关示例
C# 开关示例显示以下内容:
- case 1:有一个注释,说明只能对 case 子句提供常量表达式。
-
break;语句在具有语句的每个 事例 块中的最后一个语句之后发生,C# 需要满足此要求。
using System;
namespace CSharpSwitch2
{
class Program
{
static void Main(string[] args) // C#
{
int iEnum = 3;
switch (iEnum)
{
case 1: // Value after each 'case' must be a constant.
case 6:
Console.WriteLine("iEnum is one of these values: 1,6: " + iEnum.ToString());
break;
//case 2,3,5: // In C# this syntax is invalid, and multiple 'case' clauses are needed.
case 2:
case 3:
case 5:
Console.WriteLine("iEnum is one of these values: 2,3,5: " + iEnum.ToString());
break;
case 4:
Console.WriteLine("iEnum is one of these values: 4: " + iEnum.ToString());
break;
default:
Console.WriteLine("iEnum is an unforeseen value: " + iEnum.ToString());
break;
// All 'break' occurrences in this example are required for C# compiler.
}
return;
}
}
}
/*** Output copied from the console:
>> CSharpSwitch2.exe
iEnum is one of these values: 2,3,5: 3
>>
***/
X++、C# 比较:字符串大小写和分隔符
本部分比较了在 X++ 和 C# 中使用混合大小写处理字符串的处理。 它还介绍了 X++ 中可用的字符串分隔符。
相似 之 处
以下 X++ 功能与 C# 中的功能相同:
- 反斜杠 (\) 是字符串分隔符的转义运算符。
- 当在字符串的打开引号前立即写入 at 符号时,at 符号 (@) 将反斜杠的转义效果为 null。
- 加号 (+) 是字符串串联运算符。
Differences
下表列出了 C# 中不同 X++ 功能。
| 功能 / 特点 | X++ | C# | 注释 |
|---|---|---|---|
== 比较运算符 |
不区分: == 运算符对字符串大小写的差异不区分。 |
在 C# 中 == ,运算符对字符串大小写的差异很敏感。 |
在 X++ 中,可以使用 strCmp 函数在字符串之间进行区分大小写的比较。 |
| 字符串分隔符 | 在 X++ 中,可以使用单引号或双"引号作为字符串分隔符。注意: 最佳做法是对可能向用户显示的字符串使用双引号。 但是,当双引号是字符串中的字符之一时,使用单引号分隔字符串会很方便。 |
在 C# 中,必须使用双引号作为字符串分隔符。 这指的是类型 System.String。 |
在 X++ 和 C# 中,可以选择在文本字符串中嵌入分隔符并使用它进行转义。 在 X++ 中,还可以在用双引号(或反向)分隔的字符串中嵌入单引号,而无需使用转义。 |
| 字符分隔符 | X++ 具有字符串数据类型 (str),但没有字符类型。 |
在 C# 中,必须使用单引号作为字符分隔符。 这指的是类型 System.Char。 |
在 .NET Framework 中,其 System.String 长度与字符不同 System.Char 。 |
示例 1:== 运算符的区分大小写
==和 != 运算符在 X++ 中不区分大小写,但在 C# 中区分大小写,如以下示例所示。
| X++ | C# | 注释 |
|---|---|---|
"HELLO" == "hello" 在 X++ 中为 True。 |
"HELLO" == "hello" C# 中的 False。 |
X++ 和 C# 之间的不同大小写比较。 |
示例 2:+ 字符串串联运算符
+ 和 += 运算符用于连接 X++ 和 C# 中的字符串,如下表中的示例所示。
| X++ | C# | 注释 |
|---|---|---|
myString1 = "Hello" + " world"; 结果相等: myString1 == "Hello world" |
(与 X++相同)。 | 在 X++ 和 C# 中,+ 运算符的行为取决于其作数的数据类型。 运算符连接字符串或添加数字。 |
mystring2 = "Hello"; myString2 += " world"; 结果相等: myString2 == "Hello world" |
(与 X++相同)。 | 在 X++ 和 C# 中,以下语句是等效的: a = a + b; a += b; |
示例 3:嵌入和转义字符串分隔符
单引号或双引号可用于在 X++ 中分隔字符串。 转义字符 (\) 可用于在字符串中嵌入分隔符。 下表对此进行了说明。
| X++ | C# | 注释 |
|---|---|---|
myString1 = "They said \"yes\"."; 结果: They said "yes". |
(与 X++相同)。 | 转义字符使你可以在字符串中嵌入字符串分隔符。 |
myString2 = 'They said "yes".'; 结果: They said "yes". |
C# 语法不允许单引号分隔字符串。 | 对于用户可能看到的字符串,最佳做法是使用转义字符而不是单引号,如示例中所示。 |
myString3 = "They said 'yes'."; 结果: They said 'yes'. |
(与 X++相同)。 | 在 X++ 中,除非字符串以单引号分隔符开头,否则单引号不会被视为分隔符。 在 C# 中,单引号对字符串没有特殊含义,不能用于分隔字符串。 在 C# 中,单引号是类型 System.Char文本所需的分隔符。 X++ 没有字符数据类型。 |
str myString4 = 'C'; 此处,单引号是字符串分隔符。 |
char myChar4 = 'C'; 此处,单引号是 System.Char 分隔符,而不是 System.String 分隔符。 |
X++ 没有与 System.Char .NET Framework 中对应的数据类型。 限制为长度的 X++ 字符串仍然是字符串,而不是字符数据类型。 |
示例 4:单个转义字符
下表显示了输入或输出中的单个转义字符的示例。
| X++ | C# | 注释 |
|---|---|---|
myString1 = "Red\ shoe"; 结果: Red shoe |
C# 中的文本字符串不能包含转义后跟空格的两个字符序列,例如“\ ”。 发生编译器错误。 | 当 X++ 编译器遇到“\”的两个字符序列时,它将放弃单一转义字符。 |
myString2 = "Red\\ shoe"; 结果: Red\ shoe |
(与 X++相同)。 | 在一对转义字符中,第一个反击第二个转义字符的特殊含义。 |
比较:数组语法
X++ 与 C# 中数组的功能和语法存在相似性和差异。
相似 之 处
总的来说,X++ 和 C# 中数组的语法和处理方式非常相似。 但是,存在许多差异。
Differences
下表列出了 X++ 和 C# 不同数组的 [] 语法中的区域。
| 类别 | X++ | C# | 注释 |
|---|---|---|---|
| 声明 | 用追加到变量名称的方括号声明数组。 | 用追加到数据类型的方括号声明数组。 | int myInts[]; // X++ 注意: X++ 数组不能是方法中的参数。
|
| 声明 | 数组语法仅支持基元数据类型,例如 int 和 str。 语法不支持类或表。 |
数组语法支持基元数据类型和类。 | 在 X++ 中, Array 可以将数组用于对象数组。 |
| 声明 | X++ 仅限于单个维度数组(myStrings[8])。 | C# 添加了对多维数组(myStrings[8,3])和交错数组的支持(myStrings[8][3])。 | 在 X++ 中,不能有数组数组。 但是,存在用于限制大型数组可以使用的活动内存量的高级语法,这类似于 C# 中的多维语法:int intArray[1024,16];。 有关详细信息,请参阅最佳做法性能优化:将数组交换到磁盘。 |
| 声明 | 在 X++ 中,数组是一个特殊的构造,但它不是对象。 | 在 C# 中,所有数组都是对象,无论语法变化如何。 | X++ 确实具有 Array 类,但其基础机制与使用 [] 语法创建的数组不同。 在 C# 中,所有数组都使用相同的基础机制,无论类的 System.Array [] 语法是否在代码中使用。 |
| 长度 | 在 X++ 中,静态大小数组的长度在声明语法中确定。 | 在 C# 中,数组的大小在构造数组对象时确定。 | 在 X++ 中使用 [] 声明语法时,在为数组赋值之前无需进行更多准备。 在 C# 中,必须先声明并构造数组,然后再将其分配给该数组。 |
| 长度 | X++ 数组可以具有动态长度,即使在填充开始后也可以增加。 仅当在 [] 中声明数组时没有数字时,才适用。 如果动态数组的长度多次增加,性能可能会变慢。 | 在 C# 中,设置长度后无法更改数组的长度。 | 在以下 X++ 代码片段中,只有数组是动态的 myInts ,并且可以增加大小。 int myInts[]; int myBools[5]; myInts[2] = 12; myInts[3] = 13; myBools[6] = 26; //Error |
| 长度 | 可以使用函数 dimOf 获取某些数组的长度。 |
C# 数组是具有 Length 属性的对象。 |
无注释。 |
| 索引 | 数组索引基于 1。 | 数组索引基于 0。 | mtIntArray[0] 将导致 X++ 中的错误。 |
| 恒定 | 在 X++ 中,常量值最好是使用 #define 预编译程序指令来实现的。 | 在 C# 中,可以使用关键字 常量修饰变量声明,以实现常量值。 | X++ 没有 常量 关键字。 C# 无法将其 #define 预编译程序指令创建的变量赋值。 |
X++ 和 C# 示例
以下代码示例演示如何处理基元数据类型的数组。 第一个示例在 X++ 中,第二个示例位于 C# 中。 这两个示例都取得了相同的结果。
X++ 示例
static void JobRs005a_ArraySimple(Args _args)
{
#define.macroArrayLength(3)
// Static length.
str sSports[#macroArrayLength];
// Dynamic length, changeable during run time.
int years[];
int xx;
Global::warning("-------- SPORTS --------");
sSports[#macroArrayLength] = "Baseball";
for (xx=1; xx <= #macroArrayLength; xx++)
{
info(int2str(xx) + " , [" + sSports[xx] + "]");
}
warning("-------- YEARS --------");
years[ 4] = 2008;
years[10] = 1930;
for (xx=1; xx <= 10; xx++)
{
info(int2str(xx) + " , " + int2str(years[xx]));
}
}
输出
Infolog 的输出如下所示:
Message (14:16:08)
-------- SPORTS --------
1 , []
2 , []
3 , [Baseball]
-------- YEARS --------
1 , 0
2 , 0
3 , 0
4 , 2008
5 , 0
6 , 0
7 , 0
8 , 0
9 , 0
10 , 1930
C# 示例
using System;
public class Pgm_CSharp
{
static public void Main( string[] args )
{
new Pgm_CSharp().ArraySimple();
}
private void ArraySimple()
{
const int const_iMacroArrayLength = 3;
// In C# the length is set at construction during run.
string[] sSports;
int[] years;
int xx;
Console.WriteLine("-------- SPORTS --------");
sSports = new string[const_iMacroArrayLength];
sSports[const_iMacroArrayLength - 1] = "Baseball";
for (xx=0; xx < const_iMacroArrayLength; xx++)
{
Console.WriteLine(xx.ToString() + " , [" + sSports[xx] + "]");
}
Console.WriteLine("-------- YEARS --------");
// In C# you must construct the array before assigning to it.
years = new int[10];
years[ 4] = 2008;
years[10 - 1] = 1930;
for (xx=0; xx < 10; xx++)
{
Console.WriteLine(xx.ToString() + " , [" + years[xx].ToString() + "]");
}
}
} // EOClass
输出
从 C# 程序到命令行控制台的输出如下所示:
-------- SPORTS --------
0 , []
1 , []
2 , [Baseball]
-------- YEARS --------
0 , [0]
1 , [0]
2 , [0]
3 , [0]
4 , [2008]
5 , [0]
6 , [0]
7 , [0]
8 , [0]
9 , [1930]
其他类似数组的 X++ 功能
容器是 X++ 中提供的特殊数据类型。 它可以被视为类似于数组,也可以视为类似于 List 集合。
比较:集合
在财务和运营应用程序中,可以使用 X++ List 集合类。 在 C# 中使用的 .NET Framework 具有一个名为 .NET Framework 的 System.Collections.Generic.List类似类。
比较列表类的使用
下表将 X++ List 类上的方法与 .NET Framework 和 C# 中 System.Collections.Generic.List 的方法进行比较。
| 功能 / 特点 | X++ | C# | 注释 |
|---|---|---|---|
| 集合声明 | List myList; |
List<string> myList; |
X++ 声明不包括要存储的元素的类型。 |
| 迭代器的声明 | ListIterator iterListEnumerator enumer; |
IEnumerator<字符串> 迭代器; | 在 X++ 中,ListIterator对象具有可从中insert获取delete和List项的方法。 X++ ListEnumerator 无法修改 . 的内容 List。 在 X++ 中,对象 ListEnumerator 始终在与 .. 相同的层 List上创建。 这并非总是如此 ListIterator。 |
| 获取迭代器 | new ListIterator (myList)myList.getEnumerator() |
myList.GetEnumerator() |
在 X++ 和 C# 中,List 对象具有关联的枚举器的 getter 方法。 |
| 构造函数 | new List(Types::String) |
new List<string>() |
有关要存储在类中的 List 对象类型的信息提供给 X++ 和 C# 中的构造函数。 |
| 更新数据 | 枚举器 – 如果添加或删除了任何项, List 则枚举器将变为无效。迭代器 – 迭代器具有从中 List插入和删除项的方法。 迭代器保持有效。 |
枚举器 – 如果添加或删除了任何项, List 则枚举器将变为无效。 |
在 X List++ 和 C# 中添加或删除项后,枚举器将失效。 |
| 更新数据 | 在 X++ 中, List 类具有用于在列表开头或末尾添加项的方法。 |
在 C# 中,类 List 具有用于在列表中任意位置添加成员的方法。 它还具有从任何位置删除项的方法。 |
在 X++ 项中,只能由迭代器从 List 中删除。 |
示例 1:列表声明
以下代码示例位于声明 List 集合的 X++ 和 C# 中。
// X++
List listStrings ,list2 ,listMerged;
ListIterator literator;
// C#
using System;
using System.Collections.Generic;
List<string> listStrings ,list2 ,listMerged; IEnumerator<string> literator;
示例 2:列表的构造
在这两种语言中,集合存储的项类型必须在构造时指定。 对于类类型,X++ 无法获取比类型是否为类(Types::Class)更具体。 以下代码示例位于 X++ 和 C# 中。
// X++
listStrings = new List( Types::String );
// C#
listStrings = new List<string>;
示例 3:向列表添加项
在 X++ 和 C# 中,集合提供了一种方法,用于将项追加到集合末尾,以及用于插入项开始。 在 C# 中,该集合提供基于索引值在集合中的任何点插入的方法。 在 X++ 中,集合迭代器可以在其当前位置插入项。 以下代码示例位于 X++ 和 C# 中。
// X++
listStrings.addEnd ("StringBB.");
listStrings.addStart ("StringAA.");
// Iterator performs a midpoint insert at current position.
listIterator.insert ("dog");
// C#
listStrings.Add ("StringBB.");
listStrings.Insert (0 ,"StringAA.");
// Index 7 determines the insertion point.
listStrings.Insert (7 ,"dog");
示例 4:循环访问列表
X++ 和 C# 都有迭代器类,可用于逐步浏览集合中的项,如以下示例所示。
// X++
literator = new ListIterator (listStrings);
// Now the iterator points at the first item.
// The more method answers whether
// the iterator currently points
// at an item.
while (literator.more())
{
info(any2str (literator.value()));
literator.next();
}
// C#
literator = listStrings .GetEnumerator();
// Now enumerator points before the first item, not at the first item.
// The MoveNext method both advances the item pointer, and
// answers whether the pointer is pointing at an item.
while (literator.MoveNext())
{
Console.WriteLine (literator.Current);
}
示例 4b:C 中的 foreach#
在 C# 中, foreach 关键字通常用于简化循环访问列表的任务。 下面的代码示例的行为与前面的 C# 示例相同。
foreach (string currentString in listStrings)
{
Console.WriteLine(currentString);
}
示例 5:删除第二项
以下代码示例从集合中删除第二项。 在 X++ 中,这需要迭代器。 在 C# 中,集合本身提供了用于删除项的方法。
// X++
literator.begin();
literator.next();
literator.delete();
// C#
listStrings.RemoveAt(1);
示例 6:合并两个集合
以下代码示例将两个集合的内容合并为一个集合。
// X++
listStrings = List::merge(listStrings ,listStr3);
// Or use the .appendList method:
listStrings.appendList (listStr3);
// C#
listStrings.InsertRange(listStrings.Count ,listStr3);
比较:具有值的键集合
在财务和作应用程序中,可以使用 Map 集合类。 集合 Map 包含值对,键值加上数据值。 这类似于名为 System.Collections.Generic.Dictionary.NET Framework 类的 .
相似 之 处
以下列表描述了 X++ 和 C# 的相似之处,这些集合存储键值对:
- 两者都阻止重复键。
- 两者都使用枚举器(或迭代器)循环访问项。
- 这两个键值集合对象都是使用存储为键和值的类型的指定构造的。
- 两者都可以存储类对象,并且不限于存储 int 等基元。
Differences
下表介绍了与存储键值对的集合类的 X++ 和 C# 之间的差异:
| 功能 / 特点 | X++ | C# | 注释 |
|---|---|---|---|
| 重复键 | 在 X++ 中,类 Map 通过将调用方法 insert 隐式视为仅更新与键关联的值的作来防止重复键。 |
在 C# 中,尝试添加重复键时, Dictionary 类会引发异常。 |
尽管使用不同的技术,但两种语言都阻止了重复键。 |
| 删除项目 | 在 X++ 中, delete 迭代器对象上的方法用于从中 Map删除不需要的键值对。 |
在 C# 中,类 Dictionary 具有一个 remove 方法。 |
在这两种语言中,如果枚举器生命周期内修改集合项计数,则枚举器无效。 |
示例 1:Key-Value 集合的声明
在这两种语言中,必须指定键值集合存储的项的类型。 在 X++ 中,在构造时指定类型。 在 C# 中,在声明时和构造时都指定了类型。 以下代码示例位于 X++ 和 C# 中。
// X++
Map mapKeyValue;
MapEnumerator enumer;
MapIterator mapIter;
// C#
Dictionary<int,string> dictKeyValue;
IEnumerator<SysCollGen.KeyValuePair<int,string>> enumer;
KeyValuePair<int,string> kvpCurrentKeyValuePair;
示例 2:集合的构造
在这两种语言中,键值集合在构造期间存储的项的类型。 对于类类型,X++ 无法获取比类型是否为类(Types::Class)更具体。 以下代码示例位于 X++ 和 C# 中。
// X++
mapKeyValue = new Map(Types::Integer, Types::String);
// C#
dictKeyValue = new Dictionary<int,string>();
示例 3:向集合中添加项
在 X++ 和 C# 中的键值集合中添加项的方式几乎没有区别,如以下代码示例所示。
// X++
mapKeyValue.insert(xx ,int2str(xx) + “_Value”);
// C#
dictKeyValue.Add(xx ,xx.ToString() + “_Value”);
示例 4:循环访问 Key-Value 集合
枚举器用于循环访问 X++ 和 C# 中的键值集合,如以下代码示例所示。
// X++
enumer = mapKeyValue.getEnumerator();
while (enumer.moveNext())
{
iCurrentKey = enumer.currentKey();
sCurrentValue = enumer.currentValue();
// Display key and value here.
}
// C#
enumer = dictKeyValue.GetEnumerator();
while (enumer.MoveNext())
{
kvpCurrentKeyValuePair = enumer.Current;
// Display .Key and .Value properties=
// of kvpCurrentKeyValuePair here.
}
示例 5:更新与密钥关联的值
对于与给定键关联的值更新,这两种语言的语法大相径庭。 ollowing 代码示例适用于密钥 102。
// X++
mapKeyValue.insert(
102 ,
”.insert(), Re-inserted” + ” key 102 with a different value.”);
// C#
dictKeyValue[102] =
“The semi-hidden .item property in C#, Updated the value for key 102.”;
示例 6:删除一个项目
在循环访问集合成员时,两种语言之间的语法非常不同,用于从集合中删除一个键值对。 下面显示了密钥 102 的代码示例。
// X++
mapIter = new MapIterator(mapKeyValue);
//mapIter.begin();
while (mapIter.more())
{
iCurrentKey = mapIter.key();
if (104 == iCurrentKey)
{
// mapKeyValue.remove would invalidate the iterator.
mapIter.delete();
break;
}
mapIter.next();
}
// C#
dictKeyValue.Remove(104);
比较:异常
在比较 X++ 和 C# 之间的异常相关行为时,存在一些相似之处,但存在许多差异。 try、catch 和 throw 关键字在 X++ 和 C# 中的行为相同。 但引发和捕获的异常类型对于这两种语言不同。
相似 之 处
X++ 和 C# 与其异常功能的相似之处包括以下示例:
- 这两种语言具有相同的 try 关键字。
- 两者都具有相同的 catch 关键字。
- 这两者都启用未指定任何特定异常的 catch 语句。 此类 catch 语句捕获到达它的所有异常。
- 两者都具有相同的 throw 关键字。
Differences
下表介绍了与 X++ 和 C# 之间的异常相关的差异。
| 功能 / 特点 | X++ | C# | 注释 |
|---|---|---|---|
| 重试 | 跳转到关联的 try 块中的第一个指令。 有关详细信息,请参阅使用 try 和 catch Keyword 的异常处理。 | 重试关键字的功能可以在 C# 代码中模拟,但没有相应的关键字。 | 只有 X++ 具有 重试 关键字。 C# 没有对应项。 有关详细信息,请参阅 X++、C# 比较:异常后自动重试。 |
| 最后 |
finally支持关键字遵循try和catch关键字。 |
最后一个关键字标记了 try 和 catch 块后面的代码块。 无论引发还是捕获任何异常,最终都将执行。 | 语义与 C# 中的语义相同。 |
| 特定异常 | 在 X++ 中,异常是枚举的 Exception 元素,例如 Error、 Deadlock 或 CodeAccessSecurity。 没有异常可以包含另一个。 |
在 C# 中,异常是基类的 System.Exception 实例,或者从基类继承的任何类。 异常可以包含在引发的异常的属性中 InnerException 。 |
在 X++ 中,每个引发的异常都是异常枚举的值。 有关详细信息,请参阅异常枚举。 |
| 异常消息 | 在 X++ 中,在引发异常时创建的消息仅在 Infolog 中可用,并且消息不直接绑定到异常。 | 在 C# 中,消息是 Message 对象的成员 System.Exception 。 |
在 X++ 中,Global::error 方法是在 Infolog 中显示异常消息的机制。 有关详细信息,请参阅使用 try 和 catch Keyword 的异常处理。 |
| 异常条件 | 在 X++ 中,对尚未向其分配任何内容的对象变量调用实例方法时,会发生错误。 但是,不会随此错误一起引发异常。 因此,即使未分配的变量在块中catch被滥用,任何try块都无法获得控制。 在下面的代码示例中,代码 box4.toString(); 引起的错误不会导致控制转移到任何 catch 块: DialogBox box4;try { box4.toString(); info("toString did not error, but expected an error."); } catch (Exception::Error) // No Exception 值捕获此值。 { info("Invalid use of box4 gave control to catch, unexpected."); } |
在 C# 中,当未初始化的变量被视为对象引用时,将引发 a System.NullReferenceException 。 |
引发异常的条件可能存在其他几个差异。 |
| SQL 事务 | 在 X++ 中,当 ttsBegin - ttsCommit 事务中发生 SQL 异常时,事务块内没有 catch 语句可以处理异常。 | 在 C# 中,SQL 事务内的 catch 块可以捕获异常。 |
例子
演示了以下 X++ 功能:
- try keyword.
- catch 关键字。
- 发生 Exception::Error 异常后的行为。
X++ 示例
// X++
static void JobRs008a_Exceptions(Args _args)
{
str sStrings[4];
int iIndex = 77;
try
{
info("On purpose, this uses an invalid index for this array: " + sStrings[iIndex]);
warning("This message doesn't appear in the Infolog," + " it's unreached code.");
}
// Next is a catch for some of the values of
// the X++ Exception enumeration.
catch (Exception::CodeAccessSecurity)
{
info("In catch block for -- Exception::CodeAccessSecurity");
}
catch (Exception::Error)
{
info("In catch block for -- Exception::Error");
}
catch (Exception::Warning)
{
info("In catch block for -- Exception::Warning");
}
catch
{
info("This last 'catch' is of an unspecified exception.");
}
//finally
//{
// //Global::Warning("'finally' is not an X++ keyword, although it's in C#.");
//}
info("End of program.");
}
输出
下面是 Infolog 窗口中的输出:
Message (18:07:24)
Error executing code: Array index 77 is out of bounds.
Stack trace
(C)\Jobs\JobRs008a_Exceptions - line 8
In catch block for -- Exception::Error
End of program.
C# 示例
以下 C# 程序是对以前的 X++ 程序的重写。
// C#
using System;
public class Pgm_CSharp
{
static void Main( string[] args )
{
new Pgm_CSharp().Rs008a_CSharp_Exceptions();
}
void Rs008a_CSharp_Exceptions()
{
//str sStrings[4];
string[] sStrings = new string[4];
try
{
Console.WriteLine("On purpose, this uses an invalid index for this array: " + sStrings[77]);
Console.Error.WriteLine("This message doesn't appear in the Infolog, it's unreached code.");
}
catch (NullReferenceException exc)
{
Console.WriteLine("(e1) In catch block for -- " + exc.GetType().ToString() );
}
catch (IndexOutOfRangeException exc)
{
Console.WriteLine("(e2) In catch block for -- " + exc.GetType().ToString() );
}
// In C#, System.Exception is the base of all
// .NET Framework exception classes.
// No as yet uncaught exception can get beyond
// this next catch.
catch (Exception exc)
{
Console.WriteLine("This last 'catch' is of the abstract base type Exception: "
+ exc.GetType().ToString());
}
// The preceding catch of System.Exception makes this catch of
// an unspecified exception redundant and unnecessary.
//catch
//{
// Console.WriteLine("This last 'catch' is"
// + " of an unspecified exception.");
//}
finally
{
Console.WriteLine("'finally' is not an X++ keyword, although it's in C#.");
}
Console.WriteLine("End of program.");
}
} // EOClass
输出
下面是 C# 控制台的输出:
(e2) In catch block for -- System.IndexOutOfRangeException
'finally' is not an X++ keyword, although it's in C#.
End of program.
比较:异常后自动重试
有时,可以在 catch 块中编写代码,以修复运行时发生的异常的原因。 X++ 提供了 一个重试 关键字,该关键字只能在 catch 块内使用。 重试关键字使程序能够在 catch 块中的代码更正问题后跳转回 try 块的开头。 C# 没有 重试 关键字。 但是,可以编写 C# 代码以提供等效的行为。
重试的代码示例
以下 X++ 示例程序会导致引发 Exception::Error。 首次尝试使用无效索引值从 sStrings 数组中读取元素时,将发生这种情况。 捕获异常时,在 catch 块内的运行时将采取纠正措施。 然后重试语句跳回 try 块中的第一个语句。 第二次迭代无需遇到任何异常即可运行。
static void JobRs008b_ExceptionsAndRetry(Args _args)
{
str sStrings[4];
str sTemp;
int iIndex = 0;
sStrings[1] = "First array element.";
try
{
print("At top of try block: " + int2str(iIndex));
sTemp = sStrings[iIndex];
print( "The array element is: " + sTemp );
}
catch (Exception::Error)
{
print("In catch of -- Exception::Error (will retry)." + " Entering catch.");
++iIndex;
print("In catch of -- Exception::Error (will retry)." + " Leaving catch.");
// Here is the retry statement.
retry;
}
print("End of X++ retry program.");
pause;
}
输出
下面是“打印”窗口的输出:
At top of try block: 0
In catch of -- Exception::Error (will retry). Entering catch.
In catch of -- Exception::Error (will retry). Leaving catch.
At top of try block: 1
The array element is: First array element.
End of X++ retry program.
C# 示例
以下 C# 示例不是上一 X++ 示例中的逐行转换。 相反,C# 程序具有不同的结构,以便它模仿 X++ 程序所依赖的 重试 关键字的行为。 try 和 catch 块位于调用的方法中。 try 块中使用的变量存储在调用方方法中。 调用方方法将变量作为用 ref 关键字修饰的参数传递,以便可以在被调用方法的 catch 块内更正其值。 调用的方法将捕获所有异常,并返回 一个布尔 值,以与调用方通信,无论是否需要第二次调用。
// C#
using System;
public class Pgm_CSharp
{
static void Main(string[] args)
{
new Pgm_CSharp() .Rs008b_CSharp_ExceptionsAndRetry();
}
void Rs008b_CSharp_ExceptionsAndRetry() // Caller
{
int iIndex = -1
, iNumRetriesAllowed = 3;
bool bReturnCode = true; // Means call the callee method.
for (int xx=0; xx <= iNumRetriesAllowed; xx++)
{
if (bReturnCode)
{
bReturnCode = this.Rs008b_CSharp_ExceptionsAndRetry_Callee(ref iIndex);
}
else
{
break;
}
}
Console.WriteLine("End of C# caller method.");
}
private bool Rs008b_CSharp_ExceptionsAndRetry_Callee(ref int iIndex)
{
bool bReturnCode = true; // Means call this method again.
string[] sStrings = new string[4];
string sTemp;
sStrings[0] = "First array element.";
try
{
Console.WriteLine("At top of try block: " + iIndex.ToString());
sTemp = sStrings[iIndex];
Console.WriteLine( "The array element is: " + sTemp );
bReturnCode = false; // Means do not call this method again.
}
catch (Exception)
{
Console.WriteLine("In catch of -- Exception. Entering catch.");
++iIndex; // The 'ref' parameter in C#.
Console.WriteLine("In catch of -- Exception. Leaving catch.");
//retry;
// In C# we let the caller method do the work
// that the retry keyword does in X++.
}
Console.WriteLine("End of C# callee method.");
return bReturnCode;
}
}
输出
下面是控制台的输出:
At top of try block: -1
In catch of -- Exception. Entering catch.
In catch of -- Exception. Leaving catch.
End of C# callee method.
At top of try block: 0
The array element is: First array element.
End of C# callee method.
End of C# caller method.
比较:运算符
本部分比较 X++ 和 C# 之间的运算符。
赋值运算符
下表显示了 X++ 和 C# 中的赋值运算符之间的差异。
| X++ 和 C# | Differences |
|---|---|
= |
在 X++ 中,每当发生精度损失时,此运算符会导致隐式转换,例如,从 int64 赋值到 int。但在 C# 中,分配会导致编译错误。 |
+= 和 -= |
唯一的区别在于,在 C# 中,这些运算符也用于委托作。 |
| ++ 和 -- | 这些是这两种语言的递增和递减运算符。 这两种语言的以下行相同:++myInteger;但在 X++ 中,这两个运算符用于语句,而不是表达式。 因此,以下行在 X++ 中生成编译错误: myStr = int2str(++myInteger);myIntA = myIntBB++; |
算术运算符
下表列出了算术运算符。
| X++ 和 C# | Differences |
|---|---|
| * | 乘法运算符没有差异。 注意: 星号也用于作为 X++ 语言一部分的 SQL 语句。 在这些 SQL 语句中,星号也可以是下列项之一:
|
/ |
除法运算符在 X++ 和 C# 中是相同的。 |
MOD |
对于模数运算,唯一的区别是 c# 中使用 % 符号。 |
| + | 加法运算符在 X++ 和 C# 中是相同的。 加号还用于字符串串联。 此运算符添加数字并连接这两种语言的字符串。 |
| - | 减法运算符在 X++ 和 C# 中是相同的。 |
按位运算符
下表比较了 X++ 和 C# 之间的按位运算符。
| X++ 和 C# | Differences |
|---|---|
| << | 左移运算符在 X++ 和 C# 中是相同的。 |
| >> | 右移运算符在 X++ 和 C# 中是相同的。 |
| ~ | 按位 NOT 运算符在 X++ 和 C# 中是相同的。 |
| & | 二进制 AND 运算符在 X++ 和 C# 中是相同的。 |
| ^ | 二进制 XOR 运算符在 X++ 和 C# 中是相同的。 |
关系运算符
以下关系运算符在 X++ 和 C# 中是相同的:
==<=<=><!=&&||!? :
比较:事件
X++ 和 C# 如何实现事件设计模式存在一些差异。 有关详细信息,请参阅事件术语和关键字。
X++ 和 C 之间的事件比较#
在 X++ 和 C# 中,委托用于事件的方式存在差异。
| 概念 | X++ | C# | 注释 |
|---|---|---|---|
| delegate | 在 X++ 中,委托只能声明为类上的成员。 委托不能是表中的成员。 所有委托都是其类的实例成员,而不是 静态 成员。 不能对委托声明使用任何访问修饰符,因为所有委托都是 受保护的 成员。 因此,事件只能由委托是成员的同一类中的代码引发。 但是,委托的私有性质的一个例外是,其类外部的代码可以使用 += 和 -= 运算符对委托进行作。 | 在 C# 中,每个 委托 都是一种类型,就像每个 类 都是一种类型一样。 委托独立于任何类声明。 如果没有 事件 关键字,可以将委托作为方法的参数类型,就像可以将类作为参数类型一样。 可以构造委托的实例,以便为参数值传入。 | 在 X++ 中,每个类都是一种类型,但没有委托是一种类型。 无法构造委托的实例。 任何委托都不能是方法的参数。 但是,可以创建具有委托成员的类,并且可以将类的实例作为参数值传递。 有关详细信息,请参阅 X++ 关键字。 |
| 事件 | 在 X++ 代码中,事件是下列事件之一:
|
在 C# 中, 事件 关键字用于将 委托 类型声明为类的成员。 事件关键字的效果是使委托受到保护,但仍可供 += 和 -= 运算符访问。 可以使用 += 运算符将事件处理程序方法订阅到 事件 。 在没有事件关键字的情况下,委托非常有用,作为将函数指针作为参数传递到方法的技术。 | 只能在使用 AOT 订阅方法开头之前和方法末尾之前发生的自动事件。 |
| += 和 -= 运算符 | 在 X++ 中,使用 += 运算符订阅 委托的方法。 -= 运算符取消订阅委托的方法。 | 在 C# 中,使用 += 运算符订阅事件的方法,或订阅未与事件关键字一起使用的委托。 | 委托包含对订阅委托的方法的所有对象的引用。 委托保存这些引用时,这些对象不符合垃圾回收的条件。 |
eventHandler |
在 X++ 中,当使用 += 或 -= 运算符从委托订阅或取消订阅方法时,需要使用 eventHandler 关键字。 |
System.EventHandler 是 .NET Framework 中的委托类型。 |
此术语在 X++ 中使用方式不同于 C# 或 .NET Framework 中的术语。 有关详细信息,请参阅 X++ 关键字。 |
X++ 示例
需要注意的重要事项是 X++ 示例中的以下内容:
具有
XppClass一个命名myDelegate的委托成员。注释
AOT 包含委托的节点。 节点位于 AOT > 类 > XppClass > myDelegate。 多个事件处理程序节点可以位于 myDelegate 节点下。 在运行时,由 AOT 中的节点表示的事件处理程序不能由 -= 运算符删除。
{}委托声明末尾的大括号是必需的,但它们不能包含任何代码。
有
XppClass两种方法,其参数签名与委托兼容。 一种方法是静态的。将两个兼容的方法添加到具有 += 运算符和 eventHandler 关键字的委托中。 这些语句不调用事件处理程序方法,这些语句仅将方法添加到委托。
事件由对委托的一次调用引发。
传递给委托的参数值由每个事件处理程序方法接收。
示例顶部的短 X++ 作业启动测试。
// X++
// Simple job to start the delegate event test.
static void DelegateEventTestJob()
{
XppClass::runTheTest("The information from the X++ job.");
}
// The X++ class that contains the delegate and the event handlers.
class XppClass
{
delegate void myDelegate(str _information)
{
}
public void myEventSubscriberMethod2(str _information)
{
info("X++, hello from instance event handler 2: " + _information);
}
static public void myEventSubscriberMethod3(str _information)
{
info("X++, hello from static event handler 3: " + _information);
}
static public void runTheTest(str _stringFromJob)
{
XppClass myXppClass = new XppClass();
// Subscribe two event handler methods to the delegate.
myXppClass.myDelegate += eventHandler(myXppClass.myEventSubscriberMethod2);
myXppClass.myDelegate += eventHandler(XppClass::myEventSubscriberMethod3);
// Raise the event by calling the delegate one time,
// which calls all the subscribed event handler methods.
myXppClass.myDelegate(_stringFromJob);
}
}
上一个 X++ 作业的输出如下所示:
X++, hello from static event handler
3: The information from the X++ job. X++, hello from instance event handler
2: The information from the X++ job.
C# 示例
本部分包含前一 X++ 示例的事件设计模式的 C# 代码示例。
// C#
using System;
// Define the delegate type named MyDelegate.
public delegate void MyDelegate(string _information);
public class CsClass
{
protected event MyDelegate MyEvent;
static public void Main()
{
CsClass myCsClass = new CsClass();
// Subscribe two event handler methods to the delegate.
myCsClass.MyEvent += new MyDelegate(myCsClass.MyEventSubscriberMethod2);
myCsClass.MyEvent += new MyDelegate(CsClass.MyEventSubscriberMethod3);
// Raise the event by calling the event one time, which
// then calls all the subscribed event handler methods.
myCsClass.MyEvent("The information from the C# Main.");
}
public void MyEventSubscriberMethod2(string _information)
{
Console.WriteLine("C#, hello from instance event handler 2: " + _information);
}
static public void MyEventSubscriberMethod3(string _information)
{
Console.WriteLine("C#, hello from static event handler 3: " + _information);
}
}
上一个 C# 示例的输出如下所示:
CsClass.exe C#, hello from instance event handler
2: The information from the C\# Main. C\#, hello from static event handler
3: The information from the C\# Main.
事件和 AOT
还有其他事件系统仅适用于 AOT 中的项。 有关详细信息,请参阅 AOT 中的事件处理程序节点。
比较:预编译程序指令
X++ 和 C# 为其预编译程序指令语法共享一些关键字,但含义并不总是一样的。
相似 之 处
X++ 和 C# 编译器可识别许多相同的关键字。 在大多数情况下,关键字对两种语言编译器都相同。
Differences
X++ 与 C# 中的预编译程序指令之间的根本区别是两种语言预编译程序识别的 #define 关键字。 与 C# 不同,在 X++ 中,#define 指令需要其语法中的点。 在 X++ 中,括号可用于为定义的符号提供值。 以下示例显示了这些差异:
- 在 X++中:#define。InitialYear(2003)
- 在 C# 中:#define InitialYear
一个小区别在于,在 C# 中,#字符和指令关键字之间可能存在空格和制表符,例如 # 定义测试。
相同的关键字
下表列出了 X++ 和 C# 中类似的预编译程序指令。
| 关键字 | X++ | C# | 注释 |
|---|---|---|---|
#define |
在 X++ 中,可以定义预编译程序变量名称,并且可以向该变量提供一个值。 | 在 C# 中,可以定义预编译程序变量名称,但不能为该变量提供任何值。 此外,C# 中的任何 #define 都必须出现在文件顶部,并且不能在任何代码(如 using 语句或类声明)之后发生。 | C# 编译器可以输入命令行参数 /define 来定义预编译程序变量名称,而无需在任何 C# 代码文件中定义变量。 X++ 编译器与 /define.. |
#if |
在 X++ 中,#if 可以确定预编译变量是否存在,以及变量是否具有给定值。 | 在 C# 中,#if 只能确定预编译器变量是否存在。 它无法测试任何值,因为无法分配任何值。 | |
#endif |
在 X++ 中,#endif 标记 #if 块的末尾。 它还将结束 #ifnot 块。 | 在 C# 中,无论块是否包括 #else,#endif 都会标记 #if 块的末尾。 |
具有相同处理结果的不同关键字
下表列出了在 X++ 和 C# 中以不同方式命名的预编译程序指令,但在处理时会提供相同的结果。
| X++ | C# | 注释 |
|---|---|---|
| #ifnot | #if #else | X++ 中没有 #else 指令,但 #ifnot 提供类似的功能。 在 X++中,#ifnot 可以确定预编译变量是否存在,以及变量是否没有特定的给定值。 在 C# 中,#if 可以确定当“!”时是否存在预编译程序变量 符号的前缀为变量名称。 |
//BP Deviation documented |
#pragma 警告 | 这些 X++ 和 C# 条目不相等,但存在部分相似性。 两者都禁止显示编译器警告消息。 |
| #macrolib | .C++中的 HPP 文件 | X++ 指令 #macrolib 与 a 的 X++ 指令之间存在部分相似性。C++中的 HPP 文件。 两者都可以包含多个 #define 语句。 |
预编译程序指令(X++ 独占)
下表列出了 C# 中没有直接对应项的 X++ 预编译程序指令。
| X++ | 注释 |
|---|---|
| #linenumber | #linenumber 指令用于获取行号,以便可以输出到 Infolog。 C# 指令 #line 不同,因为它的目的是设置行号。 |
| #defdec #definc | |
| #globaldefine | 在 X++ 中,#globaldefine 与 #define 之间存在一小部分差异。 区别在于,#globaldefine 永远不会覆盖通过 #define 分配给预编译器变量的当前非空值。 C# 没有与此差异类似的内容,因为在 C# 中,无法为预编译程序变量名称提供值。 |
| #localmacro #macro | 在 X++ 中,#localmacro 使你可以向预编译器变量分配多行值。 #macro 是同义词,但建议 #localmacro。 在 C# 中,#define 指令具有此功能的一部分,但它无法为预编译程序变量赋值。 |
| #globalmacro | 在 X++ 中,#globalmacro 几乎与首选 #localmacro 相同。 |
比较:面向对象的编程
面向对象的编程 (OOP) X++ 原则不同于 C# 。
概念比较
下表比较了 X++ 和 C# 之间的 OOP 原则的实现。
| 功能 / 特点 | X++ | C# | 注释 |
|---|---|---|---|
| 铸造 | X++ 语言具有关键字,用于使向下转换安全且显式。 提示:将基类变量向下转换为派生类变量时,X++ 不需要使用 as 关键字。 但是,我们建议所有向下转换语句都使用 作为 关键字。 | 对象可以向上或向下强制转换继承路径。 向下广播需要 作为 关键字。 | 有关 X++ 关键字的详细信息,请参阅表达式运算符:Is 和 As for Inheritance。 |
| 本地函数 | 方法可以包含零个或多个本地函数的声明和代码正文。 只有该方法可以调用本地函数。 | C# 3.0 支持 lambda 表达式,这些表达式与匿名函数和本地函数相似。 Lambda 表达式通常用于委托。 | |
| 方法重载 | 不支持方法重载。 每个类只能发生一次方法名称。 | 支持方法重载。 方法名称可以在一个类中多次发生,每个情况下都有不同的参数签名。 | X++ 支持方法上的可选参数。 可选参数可以部分模拟方法重载。 有关详细信息,请参阅此表中可选参数的行。 |
| 方法重写 | 支持方法重写。 只要两种情况下的参数签名相同,派生类可以具有与基类中同名的方法。 唯一的例外是重写方法可以将默认值添加到参数。 | 支持方法重写。 虚拟 关键字必须 应用于方法,然后才能在派生类中重写该方法。 | 重写方法的概念包括方法名称、其参数签名和返回类型。 如果基方法和重写方法在上述任何方面不同,则方法重写的概念不适用。 |
| 可选参数 | 参数声明后跟默认值赋值。 方法调用方可以选择传递该参数的值,或忽略参数以接受默认值。 此功能模拟方法重载,因为对相同方法名称的两次调用可以传递不同的参数数。 具有默认值的每个参数都必须遵循没有默认值的最后一个参数。 | 参数关键字支持可选参数。 即使没有 参数 关键字,从调用方的角度来看,方法重载也可以提供部分类似的功能。 | 有关详细信息,请参阅“参数”和“使用可选参数”和“使用可选参数”。 |
| 单个继承 | 可以在 AOT 的 classDeclaration 节点中使用 extends 关键字从另一个 X++ 类派生 X++ 类。 没有类隐式派生自另一个类。 如果希望类直接派生自 Object 该类,则必须使用 extends 关键字。 只能在 extends 关键字上指定一个类。警告:修改其他类派生自的 X++ 基类时,必须使用编译转发重新编译该基类。 此选项可确保也重新编译派生类。 若要确保也重新编译派生类,请右键单击基类节点,然后单击 Add-Ins > 向前编译。 单击“生成 > 编译”(或按 F7 键)的替代方法有时不足以更改基类。 类可以实现零到多个接口。 X++ 表隐式继承自 Common 表和 xRecord 类。 |
C# 使用 extends 关键字从另一个类派生。 所有 .NET Framework 类都隐式派生自 System.Object 该类,除非它们显式派生自另一个类。 |
关键字比较
下表列出了 X++ 和 C# 中与 OOP 相关的关键字。
| 关键字 | X++ | C# | 注释 |
|---|---|---|---|
| abstract | 没有区别。 | ||
| 类 | 类声明上忽略修饰符 公共 和 私有 项。 没有命名空间对类进行分组的概念。 任何类名中都没有点(.)。 | 修饰符 公共 和 私有 可用于修改类声明。 C# 还具有关键字 内部,这与类如何在程序集文件中组合在一起。 | 没有 受保护的 类的概念,只有类的 受保护 成员。 |
| 延伸 | 类声明可以使用 extends 关键字从另一个类继承。 | 在 X++ 中使用关键字 扩展 和 实现 的冒号(:))。 | |
| 最后 | 无法在派生类中重写 最终 方法。 无法扩展 最终 类。 | 在类上 密封 的关键字意味着与 X++ 类 的最终含义相同 。 | |
| 实现 | 类声明可以使用 implements 关键字实现接口。 | ||
| interface | 接口可以指定类必须实现的方法。 | 接口可以指定类必须实现的方法。 | |
| 新增功能 |
新关键字用于分配类的新实例。 然后,会自动调用构造函数。 每个类只有一个构造函数,并且构造函数命名 new。 可以决定构造函数应输入哪些参数。 |
新关键字用于创建类的新实例。 然后,会自动调用构造函数。 构造函数方法本身没有命名 new,它们的名称与类相同。注意: 新 关键字还 可用于方法,以修改方法重写基类中相同方法的方式。 |
X++ 和 C# 都假定没有在其代码中显式编写的构造函数的类的默认构造函数。 |
| 零 | 没有区别。 | ||
| 私有和保护 | 专用和受保护的关键字可用于修改类成员的声明。 | 专用和受保护的关键字可用于修改类成员的声明。 | |
| 公共 | 未使用公共、受保护或专用修改的方法具有公共的默认访问级别。 | 未使用 公共、 受保护或 专用 修改的方法具有默认的 专用访问级别。 | |
| 静态 | 方法可以是 静态的,但字段不能。 | 方法和字段都可以 是静态的。 | |
| 超 |
超级关键字在派生类中用于访问其基类上的相同方法。 void method2(){ // Call method2 method // on the base class. super(); } |
基关键字用于派生类中访问其基类中的各种方法。 void method2() { // Call methods on // the base class. base.method2(); base.method3(); } |
在 C# 中,使用 基 函数调用基构造函数的特殊语法。 |
| 这 | 对于从一个实例方法对同一对象的另一个实例方法的调用,需要调用方法的限定符。 此关键字可用作当前对象的限定符。 | 对于从一个实例方法对同一对象的另一个实例方法的调用,不需要调用方法的限定符。 但是, 此 关键字可用作当前对象的限定符。 实际上, 此关键字可通过 显示 IntelliSense 信息来有所帮助。 | |
finalize |
类 Object 包含 finalize 该方法。 该方法 finalize 不是 最终方法,可以重写该方法。 该方法 finalize 看起来类似于 System.Object.Finalize C# 中的方法,但在 X++ 中,该方法 finalize 没有任何特殊含义。 当对对象的最后一次引用停止引用对象时,将自动从内存中删除对象。 例如,当最后一个引用超出范围或分配给另一个要引用的对象时,可能会发生这种情况。 |
这些方法和FinalizeDispose常见于某些类型的类。 垃圾回收器在销毁和对象时调用 Finalize 和 Dispose 方法。 |
在 C# 中, System.GC.Collect 可以调用 .NET Framework 中的方法来启动垃圾回收器。 X++ 中没有类似的函数,因为 X++ 使用确定性垃圾回收器。 |
main |
从菜单调用的类具有由系统调用的方法 main 。 |
从命令行控制台调用的类由系统调用其 Main 方法。 |
比较:类
在 .NET Framework 中使用 C# 时,类将分组到命名空间中。 每个命名空间都侧重于功能区域,例如文件作或反射。 但是,在 X++ 中使用类时,没有明显的分组(如命名空间)。
比较:有关反射的类
在 X++ 中, TreeNode 类提供对应用程序对象树(AOT)的访问权限。 该 TreeNode 类是 X++ 中的反射功能的中心。 类 TreeNode 及其方法可与 C# 使用的 .NET Framework 中的命名空间进行比较 System.Reflection 。
下表列出了编写 C# 代码时可用的多个类。 这些是 .NET Framework 类。 对于此表,除非另有指定,否则所有 C# 类都位于 System.Reflection 命名空间中。 每行显示编写 X++ 代码时可用的相应类或类成员。
| X++ | C# | 注释 |
|---|---|---|
TreeNode |
System .Assembly |
程序集是 C# 程序必须收集反射信息时要使用的第一类。 X++ 类 TreeNode 上的静态方法是 X++ 中反射的起点。 |
TreeNode |
System .Type |
上的 TreeNode 实例方法对应于 on System.Type的实例方法。 |
TreeNode .AOTgetSource |
MethodInfo |
该方法 AOTgetSource 在一个字符串中一起返回多个信息片段。 这包括方法中的 X++ 源代码。 相比之下, MethodInfo 每个信息都有一个单独的成员。 |
TreeNode .AOTfirstChild
TreeNode .AOTnextSibling
TreeNode .AOTiterator
AOTiterator
|
MethodInfo[] (数组) | 在 C# 中,该方法GetMethodsSystem.Type返回 MethodInfo 对象的数组。 可以通过递增索引器的常见技术循环访问数组。 相比之下,X++ 模型是导航 AOT 的树控件。 导航 TreeNode 的方法 AOTfirstChild 和 AOTnextSibling 完成。 作为等效的替代方法,X++ AOTiterator 类旨在导航 AOT 的树控件。 类节点是多个方法节点上的父节点。 分 AOTiterator 步执行子节点,以另一个实例的形式返回每个 TreeNode 节点。 命名TreeNode和AOTparent的方法的其他资源AOTprevious。 |
TreeNode .AOTgetProperty
TreeNode .AOTgetProperties
TreeNode .AOTname
|
PropertyInfo |
在 X++ 中,该方法 AOTgetProperties 返回一个长字符串,其中包含所有属性的名称 TreeNode/值对。 该方法 AOTname 返回一个字符串,该字符串仅包含 name 属性的值。 |
TreeNode .AOTsave
TreeNode .AOTinsert
|
System .Reflection .Emit (类的命名空间) |
该方法 AOTsave 将 X++ 代码中对象的更改 TreeNode 应用于 AOT,并且更改将保留。 有关大型代码示例,请参阅 TreeNode.AOTsave 方法。 |
比较:有关文件 IO 的类
有几个类执行文件输入和输出 (IO)作。 在 C# 中使用的 .NET Framework 中,这些类的对应项驻留在命名空间中 System.IO 。
下表列出了命名空间中 System.IO C# 的多个 .NET Framework 类。 表中的每一行都显示最符合 .NET Framework 类的 X++ 类或方法。
| X++ | C# | 注释 |
|---|---|---|
BinaryIo |
FileStream
BinaryReader
BinaryWriter
|
从抽象类扩展的 BinaryIo X++ 类 Io 充当流,它们还充当该流的读取器和编写器。 在 C# 中,流是独立于具有更具体读取和写入方法的类的类。 |
TextBuffer |
MemoryStream |
这些类包含内存中缓冲区,某些方法将缓冲区视为硬盘上的文件。 |
| WINAPI::createDirectory WINAPI::folderExists WINAPI::removeDirectory |
Directory
DirectoryInfo
Path
|
X++ 可以对涉及目录的许多基本作系统函数使用类中的 WINAPI 静态方法。 |
| WINAPI::getDriveType |
DriveInfo
DriveType
|
这些类和方法用于获取驱动器相关信息。 |
| WINAPI::copyFile WINAPI::createFile WINAPI::d eleteFile WINAPI::fileExists |
File
FileAttributes
FileInfo
|
X++ 可以对涉及文件的许多基本作系统函数使用类中的 WINAPI 静态方法。 |
CommaIo
Comma7Io
|
(没有相应的类。 | 这些 X++ 类可以生成Microsoft Excel 可以导入的文件。 在 X++ 中 ,EPPlus 库参考可用于与 Excel 的其他交互。 |
AsciiIo
TextIo
|
FileStream
TextReader
TextWriter
|
这些类使用不同的代码页。 |
Io |
Stream
StreamReader
StreamWriter
FileStream
|
这些常用作其他类扩展的基类。 |
CodeAccessPermission
FileIoPermission
|
System.Security
.CodeAccessPermission 命名空间 System.Security.Permissions 包括以下类:
|
这两种语言的概念和方法assertdemandrevertAssert均适用。 但是, deny C# 中可用的方法和 revertDeny 方法在 X++ 中不可用。 |
X++、ANSI SQL 比较:SQL 选择
在 X++ 中,SQL select 语句语法不同于美国国家标准研究所(ANSI)规范。
单个表选择
下表列出了 X++ SQL 和 ANSI SQL 的 select 语句之间的差异。
| 功能 / 特点 | X++ SQL | ANSI SQL | 注释 |
|---|---|---|---|
| from 子句上的表名。 |
from 子句列出了从表(如CustTable表)声明的记录缓冲区实例。 |
from 子句列出表名,而不是缓冲区的名称。 | 记录缓冲区具有类在 X++ 中具有的所有方法 xRecord。 |
| 顺序的语法顺序与 where 子句的语法顺序。 | order by 子句必须出现在 where 子句之前。 order by 子句必须出现在 from 或 join 子句之后。 group by 子句必须遵循顺序所遵循的相同语法定位规则。 | order by 子句必须出现在 where 子句之后。 where 子句必须出现在 from 或 join 子句之后。 | 在 X++ 和 ANSI SQL 中, from 和 join 子句必须出现在顺序依据和 where 子句之前。 |
| 条件求反。 | 感叹号('!')用于否定。 | not 关键字用于否定。 | X++ 不支持语法 !like。 相反,必须应用 ! 运算符到子句。 |
| like 运算符的通配符。 | 0 到许多人 - 星号 ('*') 完全 1 - 问号 ('?') |
0 到多 - 百分比符号 ('%') 完全为 1 - 底栏 ('_') |
|
| where 子句中的逻辑运算符。 | 和 - && 或 – || |
和 - 和 或 – 或 |
代码示例
下面的代码示例演示了上表中的功能。
static void OByWhere452Job(Args _args)
{
// Declare the table buffer variable.
CustTable tCustTable;
;
while
SELECT * from tCustTable
order by tCustTable.AccountNum desc
where (!(tCustTable.Name like '*i*i*') && tCustTable.Name like 'T?e *')
{
info(tCustTable.AccountNum + " , " + tCustTable.Name);
}
}
/*** InfoLog output
Message (04:02:29 pm)
4010 , The Lamp Shop
4008 , The Warehouse
4001 , The Bulb
***/
X++ SQL 关键字
以下 X++ SQL 关键字是不属于 ANSI SQL 的一部分:
- crosscompany
- firstonly100
- forceliterals
- forcenestedloop
- forceplaceholders
- forceselectorder
- validtimestate
Join 子句
下表列出了有关 X++ SQL 和 ANSI SQL 的 联接 关键字的差异。
| 功能 / 特点 | X++ SQL | ANSI SQL | 注释 |
|---|---|---|---|
| 列列表。 | 列列表中的列必须全部来自 from 子句中列出的表,而不是来自 联接 子句中的任何表。 列表中的列不能按表名进行限定。 | 列列表中的列可以来自 from 子句或 联接 子句中的任何表。 当你使用表名限定列表中的列时,它有助于其他人维护代码。 | 有关详细信息,请参阅“字段上的 Select 语句”。 |
| Join 子句语法。 | join 子句遵循 where 子句。 | join 子句遵循 from 子句中的表。 | 在 X++ 代码示例中, 联接 条件是值的相等性 SalesPoolId 。 |
| 内部 关键字。 | 默认 联接 模式是内部联接。 没有 内部 关键字。 | 默认 联接 模式是内部联接。 内部关键字可用于使代码显式。 | 外部关键字同时存在于 X++ SQL 和 ANSI SQL 中。 |
| 左右关键字。 | 左关键字和右关键字不可用。 所有联接都保留。 | 左关键字和右关键字可用于修改联接关键字。 | 无注释。 |
| 相等运算符。 | 双等号运算符 (''==) 用于测试两个值的相等性。 |
单一等号运算符 (''=) 用于测试两个值的相等性。 |
无注释。 |
代码示例
下面的代码示例演示了 X++ SQL 中的 联接 语法。
static void OByWhere453Job(Args _args)
{
// Declare table buffer variables.
CustTable tCustTable;
SalesPool tSalesPool;
;
while
SELECT
// Not allowed to qualify by table buffer.
// These fields must be from the table
// in the from clause.
AccountNum,
Name
from tCustTable
order by tCustTable.AccountNum desc
where (tCustTable.Name like 'The *')
join tSalesPool
where tCustTable.SalesPoolId == tSalesPool.SalesPoolId
{
info(tCustTable.AccountNum + " , " + tCustTable.Name);
}
}
聚合字段
下表列出了在 X++ SQL 和 ANSI SQL 之间引用 选定 列列表中的聚合字段的方式的一些差异。 聚合字段是由函数(如 sum 或 avg)派生的字段。
| 功能 / 特点 | X++ SQL | ANSI SQL | 注释 |
|---|---|---|---|
| 聚合字段名称别名。 | 聚合值位于聚合的字段中。 | 可以使用 as 关键字标记具有名称别名的聚合字段。 别名可以在后续代码中引用。 | 有关详细信息,请参阅聚合函数:X++ 和 SQL 之间的差异 |
代码示例
在下面的代码示例中,对 info 方法的调用说明了引用聚合字段的方法(请参阅 tPurchLine.QtyOrdered)。
static void Null673Job(Args _args)
{
PurchLine tPurchLine;
;
while
select
// This aggregate field cannot be assigned an alias name.
sum(QtyOrdered)
from tPurchLine
{
info(
// QtyOrdered is used to reference the sum.
"QtyOrdered: " + num2str(tPurchLine.QtyOrdered,
3, // Minimum number of output characters.
2, // Required number of decimal places in the output.
1, // '.' Separator to mark the start of the decimal places.
2 // ',' The thousands separator.
));
}
info("End.");
}
/***
Message (12:23:08 pm)
QtyOrdered: 261,550.00
End.
***/
其他差异
下表列出了 X++ SQL 和 ANSI SQL 之间的 select 语句的其他差异。
| 功能 / 特点 | X++ SQL | ANSI SQL | 注释 |
|---|---|---|---|
| 具有关键字。 | 没有 关键字 。 | 使用具有关键字可以指定组 by 子句生成的行的筛选条件。 | 无注释。 |
| Null 结果。 | 在 select 语句中,如果 where 子句筛选掉所有行,则不返回任何特殊计数行来报告该行。 | 在 选择中,如果 where 子句筛选掉所有行,则返回特殊计数行。 计数值为 0。 | 无注释。 |
| 用于导航返回行的游标。 | select 语句提供游标功能。 替代方法是使用 下一个 关键字。 | 可以声明 游标 以循环访问从 select 语句返回的行。 | |
| From 子句。 | 当未列出任何列并且仅引用一个表时, from 关键字是可选的。 以下两个语法选项是等效的: select \* from tCustTable; select tCustTable; |
除非使用 from 子句,否则 select 语句无法从表中读取。 | 在 X++ SQL 中,简单的 select 语句使用返回的第一行填充表缓冲区变量。 以下代码片段对此进行了说明: select \* from tCustTable; info(tCustTable.Name); |