{ "cells": [ { "cell_type": "markdown", "id": "40dc2502", "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "source": [ "单元格输出的格式化\n", "================" ] }, { "cell_type": "markdown", "id": "1dcd22f8", "metadata": {}, "source": [ "## 格式化输出" ] }, { "cell_type": "markdown", "id": "5ba9817e", "metadata": {}, "source": [ "基于 .NET Interactive 的工具(包括 Polyglot 笔记本、Jupyter 和其他工具)的输出是通过 .NET Interactive 格式化器生成的,这是一组位于 Microsoft.DotNet.Interactive.Formatting 命名空间下的 API。这些 API 可以作为一个 NuGet 包独立于笔记本使用。\n", "格式化器创建对象的字符串表示,这些字符串表示可以从纯文本到 HTML,再到像 JSON 和 CSV 这样的机器可读格式。\n", "\n", "以下是一些你可以在笔记本中编写的代码示例,这些代码会导致对象被格式化以供显示:\n", "- C# 单元格末尾的返回语句或尾随表达式\n", "- F# 单元格末尾的尾随表达式\n", "- 调用 Display 和 ToDisplayString 扩展方法,这些方法对 C# 和 F# 中的所有对象都可用\n", "- 在 PowerShell 单元格中调用 Out-Display\n", "\n", "格式化器还用于在多语言笔记本变量视图中格式化.NET对象的输出显示。其他语言的值格式化并不依赖于.NET。\n", "> `格式化` 指的是创建对象字符串表示的过程。此过程由.NET Interactive内核通过此处所述的API实现。当格式化后的字符串在 VS Code 或 JupyterLab 中的笔记本上显示时,这一过程被称为`渲染`" ] }, { "cell_type": "markdown", "id": "bfda8369", "metadata": {}, "source": [ "## MIME类型 与 Display显示" ] }, { "cell_type": "markdown", "id": "3800f1fa", "metadata": {}, "source": [ "对于任何一个给定的对象,可以有多种不同的字符串表示方式。这些不同的表示方式都有对应的MIME类型,这些类型由简短的字符串标识,例如`text/html`或`application/json`。MIME类型可以用来通过Display扩展方法请求特定格式的对象显示,这个方法可以用于任何对象。\n", "\n", "例如,我们可以调用rect.Display()来显示分配给变量rect的Rectangle对象" ] }, { "cell_type": "code", "execution_count": 87, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "
Submission#27+Rectangle
Width
100
Height
50
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Submission#27+Rectangle
Width
100
Height
50
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "//注意:System.Drawing不能跨平台,只在Windows系统中使用。\n", "//不知道为啥:官方要选个不跨平台的示例\n", "using System.Drawing;\n", "var ract = new Rectangle\n", "{\n", " Height = 50,\n", " Width = 100,\n", "};\n", "\n", "//默认为 text/html,下面两种方式的效果是一样的\n", "ract.Display();\n", "ract.Display(\"text/html\");" ] }, { "cell_type": "markdown", "id": "3bc132ae", "metadata": {}, "source": [ "注意,Polyglot 笔记本中默认的 MIME 类型是 text/html。这可能会因 .NET 类型的不同而有所变化,但在上述示例中,矩形类型尚未应用任何自定义设置。(将在下面展示更多关于如何进行自定义设置的内容。)\n", "> 注意:对于 C# 或 F# 中单元格的返回值,只能使用默认 MIME 类型的格式化程序。" ] }, { "cell_type": "markdown", "id": "4d2d0a0a", "metadata": {}, "source": [ "在使用Display时,可以指定不同于默认的MIME类型。只需将所需的MIME类型作为参数传递即可,例如:rect.Display(\"text/plain\")" ] }, { "cell_type": "code", "execution_count": 88, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/plain": [ "Rectangle\r\n", " Width: 100\r\n", " Height: 50" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ract.Display(\"text/plain\");" ] }, { "cell_type": "markdown", "id": "62941e56", "metadata": {}, "source": [ "另一种常见的MIME类型是application/json。在Polyglot笔记本中使用此MIME类型时,对象使用System.Text.Json进行格式化。\n", "\n", "执行下面的单元格,会以Json格式输出。JSON输出中的代码颜色由 VS Code 笔记本渲染器针对`application/json`类型提供。" ] }, { "cell_type": "code", "execution_count": 89, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "application/json": { "Height": 50, "Width": 100 } }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ract.Display(\"application/json\");" ] }, { "cell_type": "markdown", "id": "c69d8a8e", "metadata": {}, "source": [ "## 自定义格式化" ] }, { "cell_type": "markdown", "id": "d65a2d3a", "metadata": {}, "source": [ ".NET Interactive的格式化API是高度可配置的。\n", "\n", "可以使用`Microsoft.DotNet.Interactive.Formatting`中的代码,调整格式化行为的方式。" ] }, { "cell_type": "markdown", "id": "86f3a920", "metadata": {}, "source": [ "### 限制输出数量\n", "在格式化序列时,例如数组或实现IEnumerable的对象,.NET Interactive的格式化器会将它们展开,以便你可以看到其中的值。\n", "\n", "如果执行结果集太多,默认情况下只会输出前面很少项,剩余的数据以 `...More` 代替。" ] }, { "cell_type": "code", "execution_count": 90, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "
indexvalue
0
{ Id = 1, Age = 7, Name = 韦子异 }
Id
1
Age
7
Name
韦子异
1
{ Id = 2, Age = 18, Name = 姚宇宁 }
Id
2
Age
18
Name
姚宇宁
2
{ Id = 3, Age = 2, Name = 傅子异 }
Id
3
Age
2
Name
傅子异
3
{ Id = 4, Age = 29, Name = 朱子韬 }
Id
4
Age
29
Name
朱子韬
4
{ Id = 5, Age = 85, Name = 林杰宏 }
Id
5
Age
85
Name
林杰宏
5
{ Id = 6, Age = 76, Name = 胡璐 }
Id
6
Age
76
Name
胡璐
6
{ Id = 7, Age = 88, Name = 周璐 }
Id
7
Age
88
Name
周璐
7
{ Id = 8, Age = 88, Name = 田秀英 }
Id
8
Age
88
Name
田秀英
8
{ Id = 9, Age = 19, Name = 姜詩涵 }
Id
9
Age
19
Name
姜詩涵
9
{ Id = 10, Age = 44, Name = 蔡秀英 }
Id
10
Age
44
Name
蔡秀英
10
{ Id = 11, Age = 12, Name = 张睿 }
Id
11
Age
12
Name
张睿
11
{ Id = 12, Age = 94, Name = 金嘉伦 }
Id
12
Age
94
Name
金嘉伦
12
{ Id = 13, Age = 73, Name = 萧致远 }
Id
13
Age
73
Name
萧致远
13
{ Id = 14, Age = 90, Name = 赵致远 }
Id
14
Age
90
Name
赵致远
14
{ Id = 15, Age = 82, Name = 蒋宇宁 }
Id
15
Age
82
Name
蒋宇宁
15
{ Id = 16, Age = 38, Name = 顾震南 }
Id
16
Age
38
Name
顾震南
16
{ Id = 17, Age = 15, Name = 余安琪 }
Id
17
Age
15
Name
余安琪
17
{ Id = 18, Age = 74, Name = 熊岚 }
Id
18
Age
74
Name
熊岚
18
{ Id = 19, Age = 19, Name = 卢子韬 }
Id
19
Age
19
Name
卢子韬
19
{ Id = 20, Age = 44, Name = 孔杰宏 }
Id
20
Age
44
Name
孔杰宏
... (more)
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "var names = new string[]\n", "{\n", " \"吕宇宁\", \"韦子异\", \"姚宇宁\", \"傅子异\", \"朱子韬\", \"林杰宏\", \"胡璐\", \"周璐\", \"田秀英\", \"姜詩涵\", \"蔡秀英\", \"张睿\", \"金嘉伦\", \"萧致远\", \"赵致远\", \"蒋宇宁\", \"顾震南\", \"余安琪\", \"熊岚\", \"卢子韬\",\n", " \"孔杰宏\", \"周子韬\", \"黄睿\", \"史璐\", \"赵震南\", \"杜安琪\", \"赵致远\", \"石詩涵\", \"龚杰宏\", \"丁安琪\", \"黄杰宏\", \"傅睿\", \"戴嘉伦\", \"郝杰宏\", \"傅晓明\", \"孟嘉伦\", \"段睿\", \"戴致远\", \"石安琪\", \"汪詩涵\",\n", " \"贾云熙\", \"邱子韬\", \"吴杰宏\", \"贾岚\", \"曾震南\", \"许云熙\", \"吴宇宁\", \"唐岚\", \"常嘉伦\", \"曾岚\", \"袁嘉伦\", \"黄晓明\", \"韦致远\", \"莫安琪\", \"丁子韬\", \"雷云熙\", \"许秀英\", \"朱宇宁\", \"黎詩涵\", \"贾晓明\", \n", " \"孔詩涵\", \"秦宇宁\", \"方子韬\", \"邵秀英\", \"冯宇宁\", \"何晓明\", \"方嘉伦\", \"熊秀英\", \"沈云熙\", \"顾秀英\", \"许致远\", \"胡宇宁\", \"陶子异\", \"叶安琪\", \"邱震南\", \"刘子异\", \"周宇宁\", \"黄云熙\", \"龚杰宏\", \"杜秀英\", \n", " \"向子异\", \"马睿\", \"黄安琪\", \"于安琪\", \"金嘉伦\", \"龚璐\", \"杨致远\", \"戴嘉伦\", \"钟詩涵\", \"阎詩涵\", \"雷安琪\", \"宋杰宏\", \"田致远\", \"冯致远\", \"雷杰宏\", \"雷子异\", \"叶璐\", \"王子异\", \"冯子韬\", \"史宇宁\"\n", "};\n", "\n", "var students = Enumerable.Range(1,50).Select(s => new {Id = s, Age = Random.Shared.Next(1,100), Name = names[s]});\n", "students.Display();" ] }, { "cell_type": "markdown", "id": "e9e5f666", "metadata": {}, "source": [ "Formatter.ListExpansionLimit 更改为自定义值" ] }, { "cell_type": "code", "execution_count": 91, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "
indexvalue
0
{ Id = 1, Age = 18, Name = 韦子异 }
Id
1
Age
18
Name
韦子异
1
{ Id = 2, Age = 89, Name = 姚宇宁 }
Id
2
Age
89
Name
姚宇宁
2
{ Id = 3, Age = 52, Name = 傅子异 }
Id
3
Age
52
Name
傅子异
3
{ Id = 4, Age = 32, Name = 朱子韬 }
Id
4
Age
32
Name
朱子韬
4
{ Id = 5, Age = 73, Name = 林杰宏 }
Id
5
Age
73
Name
林杰宏
... (more)
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Microsoft.DotNet.Interactive.Formatting.Formatter.ListExpansionLimit = 5;\n", "students.Display();" ] }, { "cell_type": "markdown", "id": "55513f2a", "metadata": {}, "source": [ "上面的示例中,通过设置Formatter.ListExpansionLimit = 5,然后显示相同的列表对象,.NET Interactive现在仅显示前五项,后面跟着 ...(more)。\n", "\n", "也可以通过设置`Formatter.ListExpansionLimit`来限制特定类型的输出。需要注意的是,这里的类型T必须与列表中的项完全匹配。\n", "\n", "以下是一个使用int类型的示例:" ] }, { "cell_type": "code", "execution_count": 92, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "
[ 1, 2, 3 ... (7 more) ]
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Microsoft.DotNet.Interactive.Formatting.Formatter.ListExpansionLimit = 3;\n", "Enumerable.Range(1, 10).Display();\n" ] }, { "cell_type": "markdown", "id": "c30384b0", "metadata": {}, "source": [ "注意:有些以 ...(more)结尾,有些以(数字 more)结尾。\n", "\n", "这是因为:List、List、数组等,列举前就知道元素确切的数量,以 `(数字 more)`结尾;而像 IEnumerable(Enumerable.Range 返回类型是IEnumerable)之类的,因此在枚举整个序列之前,无法知道元素确切的数量;在这种情况下,.NET交互式格式化器在达到配置的ListExpansionLimit时会停止,并不会继续计数剩余的序列, 以 `...(more)` 结尾" ] }, { "cell_type": "markdown", "id": "8ce3b66a", "metadata": {}, "source": [ "### 限制对象循环引用次数" ] }, { "cell_type": "markdown", "id": "79e36f3f", "metadata": {}, "source": [ "有些对象存在引用循环。虽然.NET Interactive格式化器会遍历对象图,但它为了避免输出过大和无限递归,只会递归到特定深度。\n", "\n", "以下是一个C#代码示例,它定义了一个简单的Node类,创建了一个引用循环,并使用C#脚本的尾随表达式(相当于返回语句)对其进行格式化:" ] }, { "cell_type": "code", "execution_count": 93, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "
Submission#93+Node
Next
Submission#93+Node
Next
Submission#93+Node
Next
Submission#93+Node
Next
Submission#93+Node
Next
Submission#93+Node
NextSubmission#93+Node
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "public class Node\n", "{\n", " public Node Next { get; set; } \n", "}\n", "\n", "Node node1 = new();\n", "Node node2 = new();\n", "\n", "node1.Next = node2;\n", "node2.Next = node1;\n", "\n", "node1" ] }, { "cell_type": "markdown", "id": "0cdad28f", "metadata": {}, "source": [ "这表明格式化器在格式化到深度6后停止了递归。这个深度可以通过Formatter.RecursionLimit方法进行更改:" ] }, { "cell_type": "code", "execution_count": 94, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "
Submission#93+Node
Next
Submission#93+Node
NextSubmission#93+Node
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Microsoft.DotNet.Interactive.Formatting.Formatter.RecursionLimit = 2;\n", "node1" ] }, { "cell_type": "markdown", "id": "9ae2d29c", "metadata": {}, "source": [ "### 首选 MIME 类型" ] }, { "cell_type": "markdown", "id": "210ac005", "metadata": {}, "source": [ "前面提到,Polyglot 笔记本中用于格式化的默认 MIME 类型是 text/html。当使用 Display() 方法时,如果没有向 mimeType 参数传递值,或者在 C# 或 F# 中使用 return 语句或尾随表达式时,就会应用这个默认设置。这个默认设置可以全局更改,也可以针对特定类型更改。\n", "\n", "以下示例将 Rectangle 的默认值更改为 text/plain。" ] }, { "cell_type": "code", "execution_count": 95, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/plain": [ "Student\r\n", " Id: 1\r\n", " Name: 张三\r\n", " Age: 55" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "public class Student\n", "{\n", " public int Id {get;set;}\n", " public string Name {get;set;}\n", " public int Age {get;set;}\n", "}\n", "\n", "Microsoft.DotNet.Interactive.Formatting.Formatter.SetPreferredMimeTypesFor(typeof(Student), \"text/plain\");\n", "\n", "new Student\n", "{ \n", " Id = 1,\n", " Name = \"张三\",\n", " Age = 55\n", "}" ] }, { "cell_type": "markdown", "id": "e2d42a41", "metadata": {}, "source": [ "该方法可用于设置多个首选MIME类型。第二个参数是params参数,它允许您传递多个值。" ] }, { "cell_type": "code", "execution_count": 96, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "application/json": { "Age": 55, "Id": 1, "Name": "张三" }, "text/plain": [ "Student\r\n", " Id: 1\r\n", " Name: 张三\r\n", " Age: 55" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "Microsoft.DotNet.Interactive.Formatting.Formatter.SetPreferredMimeTypesFor\n", "(\n", " typeof(Student), \n", " \"text/plain\",\n", " \"application/json\"\n", ");\n", "\n", "new Student\n", "{ \n", " Id = 1,\n", " Name = \"张三\",\n", " Age = 55\n", "}" ] }, { "cell_type": "markdown", "id": "24305cfd", "metadata": {}, "source": [ "注册多个MIME时,可以切换输出格式:\n", "单击 执行结果单元格的最左侧`...`, 选择 `更改演示文稿`后,在VS Code最上访,会弹出选择窗口,选择注册项中的一个,就可以重新以选中项的格式重新显示结果。" ] }, { "cell_type": "markdown", "id": "058b472e", "metadata": {}, "source": [ "### 替换默认的格式化类型" ] }, { "cell_type": "markdown", "id": "15eeab9b", "metadata": {}, "source": [ "默认格式化器通常通过打印列表和属性来显示对象的值。输出主要是文本形式。如果你希望以不同的方式显示某种类型的内容,无论是不同的文本输出、图像还是图表,你可以通过为特定类型注册自定义格式化器来实现。这些类型可以是你自己定义的,也可以是其他.NET库中定义的。\n", "\n", "一个常见的使用自定义格式化器的场景是渲染图表。一些NuGet包,如Plotly.NET,提供了.NET Interactive扩展,利用此功能以交互式的HTML和JavaScript为基础生成输出。\n", "\n", "注册自定义格式化器最简单的方法是使用Formatter.Register方法,它有几个不同的重载版本。在笔记本中使用最友好、最方便的一个接受两个参数:\n", "+ 委托:它接受你要注册的类型的对象,并返回一个字符串。在这里,你可以指定所需的字符串转换;\n", "+ MIME类型:只有当使用此MIME类型时,才会调用你的自定义格式化器\n", "\n", "下面的示例将 Rectangles 类的实例格式化为SVG矩形。" ] }, { "cell_type": "code", "execution_count": 97, "id": "494ec543", "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "public class Rectangle\n", "{\n", " public int Width {get;set;}\n", " public int Height {get;set;}\n", "}\n", "\n", "Microsoft.DotNet.Interactive.Formatting.Formatter.Register\n", "(\n", " formatter: //格式化方法:把类型对象,转化为输出字符串\n", " rect => $\"\"\"\n", " \n", " \n", " \n", " \"\"\", \n", " mimeType: //输出媒体类型\n", " \"text/html\"\n", ");" ] }, { "cell_type": "code", "execution_count": 98, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "\r\n", " \r\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "/*\n", "运行此代码后,Rectangle对象将显示为图形矩形,而不是属性值列表。\n", "(以下示例使用C#脚本尾随表达式语法,该语法通常配置为在笔记本中使用text/html MIME类型。)\n", "*/\n", "new Rectangle(){Width = 100, Height = 50}" ] }, { "cell_type": "markdown", "id": "363d55e9", "metadata": {}, "source": [ "特别指出:当在列表中遇到自定义类型或作为对象属性时,仍将调用自定义格式化程序" ] }, { "cell_type": "code", "execution_count": 99, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "
indexvalue
0\r\n", " \r\n", "
1\r\n", " \r\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "new[] \n", "{\n", " new Rectangle(){Width = 200, Height = 50},\n", " new Rectangle(){Width = 200, Height = 100}\n", "}" ] }, { "cell_type": "code", "execution_count": 100, "id": "c6fd6734", "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "
{ Name = Example, Rectangle = Submission#97+Rectangle }
Name
Example
Rectangle\r\n", " \r\n", "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "//具有 Rectangle 类型属性的匿名对象\n", "new {\n", " Name = \"Example\",\n", " Rectangle = new Rectangle(){ Width=100, Height=50 }\n", "}" ] }, { "cell_type": "markdown", "id": "39dab7b0", "metadata": {}, "source": [ "### 打开泛型类型" ] }, { "cell_type": "markdown", "id": "3606e605", "metadata": {}, "source": [ "可以通过使用开放泛型类型定义作为键来指定格式化程序。以下代码将为所有类型的 T 的 List 变体注册一个格式化程序,并打印每个元素及其哈希代码。(请注意,必须强制转换对象才能迭代其项。" ] }, { "cell_type": "code", "execution_count": 101, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "Microsoft.DotNet.Interactive.Formatting.Formatter.Register\n", "(\n", " type: typeof(List<>),\n", " formatter: (list, writer) =>\n", " {\n", " foreach (var obj in (IEnumerable)list)\n", " {\n", " writer.WriteLine($\"{obj} ({obj.GetHashCode()})\");\n", " }\n", " }, \"text/html\"\n", ");" ] }, { "cell_type": "code", "execution_count": 102, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "data": { "text/html": [ "one (1556465726)\r\n", "two (401392997)\r\n", "three (-257294126)\r\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "//运行上述代码后,以下内容将不再仅打印列表中的值 [\"one\",\"two\",\"three\"]\n", "//变为:one (254814599) two (656421459) three (-1117028319)\n", "var list = new List { \"one\", \"two\", \"three\" };\n", "list" ] }, { "cell_type": "markdown", "id": "a8b71f19", "metadata": {}, "source": [ "### TypeFormatterSource 特性类\n", "Formatter.Register方式外,另一种注册自定义格式化程序的方式是:使用 TypeFormatterSourceAttribute 修饰类型。如果您想直接在笔记本中重新定义格式化程序设置,这不是最方便的方法。但是,如果您正在编写 .NET Interactive 扩展,或者编写包含某些类型的自定义格式的库或应用程序,则建议使用此方法。其中一个原因是基于属性的方法更简单。另一个原因是,调用 Formatter.ResetToDefault() 时不会清除基于属性的格式化程序自定义,而使用 Formatter.Register 配置的格式化程序会被清除。您可以将基于属性的注册方法视为为类型设置默认格式的一种方式。\n", "\n", "基于属性的格式化程序注册有两种方法:一种用于项目引用时,另一种用于项目不引用 Microsoft.DotNet.Interactive.Formatting 时\n", "\n", "如果您已经引用了 Microsoft.DotNet.Interactive.Formatting ,例如,因为您正在编写 .NET Interactive 扩展,那么您可以使用 中定义的 TypeFormatterSourceAttribute 来修饰需要自定义格式的类型 Microsoft.DotNet.Interactive.Formatting 。下面是一个示例:" ] }, { "cell_type": "code", "execution_count": 103, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "[Microsoft.DotNet.Interactive.Formatting.TypeFormatterSource(typeof(MyFormatterSource))]\n", "public class MyTypeWithCustomFormatting\n", "{\n", "\n", "}\n", "\n", "//带Mime类型\n", "[Microsoft.DotNet.Interactive.Formatting.TypeFormatterSource\n", "(\n", " typeof(MyFormatterSource), PreferredMimeTypes = new[] { \"text/html\", \"application/json\" }\n", ")]\n", "public class MyTypeWithCustomFormatting2\n", "{\n", "}\n", "\n", "//TypeFormatterSourceAttribute 指定的格式化程序源必须实现 ITypeFormatterSource,并且必须具有空构造函数。它不需要是 public 类型\n", "public class MyFormatterSource : Microsoft.DotNet.Interactive.Formatting.ITypeFormatterSource\n", "{\n", " public IEnumerable CreateTypeFormatters()\n", " {\n", " return new Microsoft.DotNet.Interactive.Formatting.ITypeFormatter[]\n", " {\n", " new Microsoft.DotNet.Interactive.Formatting.PlainTextFormatter(context => \n", " $\"Hello from {nameof(MyFormatterSource)} using MIME type text/plain\"),\n", " new Microsoft.DotNet.Interactive.Formatting.HtmlFormatter(context => \n", " $\"Hello from {nameof(MyFormatterSource)} using MIME type text/html\")\n", " };\n", " }\n", "}" ] }, { "cell_type": "markdown", "id": "6437f82e", "metadata": {}, "source": [ "一个完整例子:" ] }, { "cell_type": "code", "execution_count": 104, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "[AttributeUsage(AttributeTargets.Class)]\n", "internal class TypeFormatterSourceAttribute : Attribute\n", "{\n", " public TypeFormatterSourceAttribute(Type formatterSourceType)\n", " {\n", " FormatterSourceType = formatterSourceType;\n", " }\n", "\n", " public Type FormatterSourceType { get; }\n", "\n", " public string[] PreferredMimeTypes { get; set; }\n", "}\n", "\n", "internal class MyConventionBasedFormatterSource\n", "{\n", " public IEnumerable CreateTypeFormatters()\n", " {\n", " yield return new MyConventionBasedFormatter { MimeType = \"text/html\" };\n", " }\n", "}\n", "\n", "internal class MyConventionBasedFormatter\n", "{\n", " public string MimeType { get; set; }\n", "\n", " public bool Format(object instance, System.IO.TextWriter writer)\n", " {\n", " if (instance is MyTypeWithCustomFormatting myObj)\n", " {\n", " writer.Write($\"
Custom formattering for {myObj}
\");\n", " return true;\n", " }\n", " else\n", " {\n", " return false;\n", " }\n", " }\n", "}" ] }, { "cell_type": "markdown", "id": "fbf04c96", "metadata": {}, "source": [ "### 重置格式设置\n", "在尝试不同的格式设置配置时,您可能会发现需要将所有内容重置为首次启动内核时看到的默认值。您可以轻松执行此作:" ] }, { "cell_type": "code", "execution_count": 105, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [], "source": [ "Microsoft.DotNet.Interactive.Formatting.Formatter.ResetToDefault();" ] }, { "cell_type": "markdown", "id": "575b3a6a", "metadata": {}, "source": [ "### 如何选择格式化程序\n", "可以注册多个可能适用于同一类型的格式化程序。例如,可以为 object、IEnumerable 和 IList 注册格式化程序,其中任何一个都可能合理地应用于 List 的实例。由于这些原因,了解如何选择 formatter 可能很有用。" ] }, { "cell_type": "markdown", "id": "d4aaa243", "metadata": {}, "source": [ "为 A 类型的对象选择适用的格式化程序,如下所示:\n", "+ 选择 MIME 类型:\n", " + 选择与 A 相关的最具体的用户注册 MIME 类型首选项\n", " + 如果没有相关的用户注册的 MIME 类型,则使用配置的默认 MIME 类型\n", "+ 选择一个格式化程序:\n", " + 选择与 A 相关的最具体的用户注册格式化程序\n", " + 如果没有相关的用户注册格式化程序,则选择默认格式化程序\n", "\n", "> 在这里,“最具体”是指类和接口层次结构。如果顺序完全一致或存在其他冲突,则首选较新的注册。当泛型类型的类型定义相同时,泛型类型的类型实例化优先于泛型格式化程序。\n", "> MIME 类型的默认格式化程序集始终包括 object 的格式化程序" ] }, { "cell_type": "code", "execution_count": 107, "metadata": { "polyglot_notebook": { "kernelName": "csharp" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "
Submission#97+Rectangle
Width
100
Height
50
\r\n" ] } ], "source": [ "using System.IO;\n", "using Microsoft.DotNet.Interactive.Formatting;\n", "\n", "ITypeFormatter formatter = Formatter.GetPreferredFormatterFor( typeof(Rectangle), Formatter.DefaultMimeType);\n", "\n", "var rect = new Rectangle { Width = 100, Height = 50 };\n", "\n", "var writer = new StringWriter();\n", "\n", "formatter.Format(rect, writer);\n", "\n", "Console.WriteLine(writer.ToString());" ] }, { "cell_type": "markdown", "id": "6cbb3edb", "metadata": {}, "source": [ "Examples 例子, 用于说明 Formatter 选择的工作原理\n", "\n", "+ 如果您为类型 A 注册格式化程序,则该格式化程序将用于类型 A 的所有对象(直到稍后指定类型 A 的替代格式化程序)\n", "+ 如果为 System.Object 注册格式化程序,则它优先于所有其他格式化程序,但其他更具体的用户定义的格式化程序除外\n", "+ 如果为任何 sealed 类型注册格式化程序,则它优先于所有其他格式化程序(除非为该类型指定了更多格式化程序)\n", "+ 如果注册 List<> 和 List 格式化程序,则 List 格式化程序优先用于 List 类型的对象,而 List<> 格式化程序优先用于其他泛型实例化,例如 List" ] } ], "metadata": { "kernelspec": { "display_name": ".NET (C#)", "language": "C#", "name": ".net-csharp" }, "polyglot_notebook": { "kernelInfo": { "defaultKernelName": "csharp", "items": [ { "aliases": [], "languageName": "csharp", "name": "csharp" } ] } } }, "nbformat": 4, "nbformat_minor": 5 }