读书笔记: C# 7.0 in a nutshell (第 四 章 Advanced C#- 下)

本文深入探讨了C#中的高级特性,包括元组、属性、动态绑定、运算符重载等,详细解释了它们的工作原理及应用场景,为C#开发者提供了深入理解和应用这些特性的指南。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内容:

第四章(下): Advanced C#

  1. Tuple(C# 7.0)
  2. Atrribute
  3. Caller Info Attributes
  4. Dynamic Binding
  5. 运算符重载
  6. Unsafe code and Pointer
  7. Preprocessor Directives
  8. XML Documentation

1. Tuples (C# 7)

Tuples的主要作用是提供一种类型安全的,能让一个方法返回多个值而不是用out参数 的机制。

var bob = ("Bob", 23);   //创建了一个无名tuple,让compiler来推断类型,通过Item1,Item2来访问
Console.WriteLine (bob.Item1); // Bob
Console.WriteLine (bob.Item2); // 23

C# 7的tuple功能依赖于一系列的 generic structs ,叫做 System.ValueTuple<> , 他们并不是 .NET 4.6框架的一部分,而是包含在程序集System.ValueTuple中,需要使用Nuget来添加使用。

a. Tuples是值类型, 但是它的内容是可读可写的 :
var joe = bob; // joe is a *copy* of job
joe.Item1 = "Joe"; // Change joe's Item1 from Bob to Joe
Console.WriteLine (bob); // (Bob, 23)
Console.WriteLine (joe); // (Joe, 23)
b.明确指定tuple的类型:
(string,int) bob = ("Bob", 23); // var is not compulsory with tuples!

static (string,int) GetPerson() => ("Bob", 23);
c. 泛型使用tuple:
Task<(string,int)>
Dictionary<(string,int),Uri>
IEnumerable<(int ID, string Name)> // See below for naming elements

1.1 命名Tuple

var tuple = (Name:"Bob", Age:23);
Console.WriteLine (tuple.Name); // Bob
Console.WriteLine (tuple.Age); // 23

// 函数返回中指定名字
static (string Name, int Age) GetPerson() => ("Bob", 23);
static void Main()
{
    var person = GetPerson();
    Console.WriteLine (person.Name); // Bob
    Console.WriteLine (person.Age); // 23
}
a. tuple的元素类型一致时,是可以兼容的
(string Name, int Age, char Sex) bob1 = ("Bob", 23, 'M');
(string Age, int Sex, char Name) bob2 = bob1; // No error!

//这容易造成使用误解
Console.WriteLine (bob2.Name); // M
Console.WriteLine (bob2.Age); // Bob
Console.WriteLine (bob2.Sex); // 23
b. 类型擦除:

之前提到过,C#编译器在处理匿名类型的时候,是把他编译成一个类,然后其中的property就是他们的字段。而C#对待Tuple则不是这样,它利用的是已经存在的一系列的结构体

public struct ValueTuple<T1>
public struct ValueTuple<T1,T2>
public struct ValueTuple<T1,T2,T3>
...

所以比如 (string, int)这个tuple就相当于是一种 ValueTuple<string, int>。而对于命名tuple,在底层实际是没有对应类型的, name的这些属性实际上只存在于 源代码中,是编译器的一种魔术,编译器编译之后,names大多数情况下就消失了,去查看编译后的结果,命名tuple在访问元素时,还是使用的 Item1,Item2...

所以在大多数情况下,无法使用反射来在运行时获取tuple的名称。

We said that the names mostly disappear, because there’s an exception. With methods/properties that return named tuple types, the compiler emits the element names by applying a custom attribute called TupleElementNamesAttribute (see “Attributes” on page 186) to the member’s return type. This allows named elements to work when calling methods in a different assembly (for which the compiler does not have the source code).

1.2 ValueTuple.Create

ValueTuple<string,int> bob1 = ValueTuple.Create ("Bob", 23);
(string,int) bob2 = ValueTuple.Create ("Bob", 23);

1.3 Deconstructing Tuples

Tuple默认就支持 Deconstruction

var bob = ("Bob", 23);
(string name, int age) = bob; // Deconstruct the bob tuple into

命名tuple之间的语法差异:

(string name, int age) = bob; // Deconstructing a tuple
(string name, int age) bob2 = bob; // Declaring a new tuple

1.4 相等性比较

就像匿名类型一样, ValueTuple<> 也重写了Equals方法,所以可以用来比较,也可以用来当做字典的Key:

var t1 = ("one", 1);
var t2 = ("one", 1);
Console.WriteLine (t1.Equals (t2)); // True
Console.WriteLine(t1 == t2);  //编译错误,C# 7.0 中不支持功能“元组相等”。请使用 7.3 或更高的语言版本。

1.5 The System.Tuple Classes

System名称空间中有一个类叫做Tuple,这是在 .NET 4.0引入的类,它是一个class,而不是一个 struct。 对tuple的使用来说,struct在性能上比class好,而且几乎没有缺点,所以微软在添加新的 Tuple特性的时候,直接加入了ValueTuple,而忽略了以前Tuple类。

Tuple<string,int> t = Tuple.Create ("Bob", 23); // Factory method
Console.WriteLine (t.Item1); // Bob
Console.WriteLine (t.Item2); // 23

2. Attributes

Attributes是一种对提供 自定义信息的扩展机制。当需要和 类型系统 有很强关联关系的时候,非常有用。一个非常好的Attribute的例子就是Serialization, 它能指定每个字段如何格式化来 Serialize。 如何自定义Attribute在Chap19讲。

2.1 Attribute Classes

一个Attribute定义成抽象类System.Attribute的实现类。惯例上所以Attribute类型都以XXXAttribute来命名。C#会识别这些命名方式,然后让你可以省去Attribute这个名字部分:

[ObsoleteAttribute]
public class Foo {...}
//相当于
[Obsolete]
public class Foo {...}

2.2 Named and Positional Attribute Parameters

Attribute使用的时候可以有参数:

[XmlElement ("Customer", Namespace="http://oreilly.com")]
public class CustomerEntity { ... }

参数有2类,positionalnamed,位置参数相当于 Attribute类型的公开构造器的参数,而命名参数相当于 public字段或者public属性。

2.3 Attribute Targets

Attribute有使用的Target,通常是它紧挨着的 Type或者Type成员(字段等)。同时,Attribute也可以运用在程序集上,这需要明确指定这个Attribute的Target

示例: 使用CLSCompliantAtrribute对整个程序集生效:

[assembly:CLSCompliant(true)]

2.4 指定多个Attributes

[Serializable, Obsolete, CLSCompliant(false)]
public class Bar {...}

[Serializable] [Obsolete] [CLSCompliant(false)]
public class Bar {...}

[Serializable, Obsolete]
[CLSCompliant(false)]
public class Bar {...}

3. Caller Info Attributes

从C#5开始,可以给 可选参数提供下面3种 Caller info Attributes,来让编译器,把调用方的信息传入这个参数中:

  • [CallerMemberName] 调用方 的成员名(member name)
  • [CallerFilePath] 调用方 源代码文件的路径名
  • [CallerLineNumber] 调用方源代码文件中所在的行数

例子:

using System;
using System.Runtime.CompilerServices;
class Program
{
    static void Main() => Foo();

    static void Foo (
        [CallerMemberName] string memberName = null,
        [CallerFilePath] string filePath = null,
        [CallerLineNumber] int lineNumber = 0)
    {
        Console.WriteLine (memberName);    //Main
        Console.WriteLine (filePath);     // c:\source\test\Program.cs
        Console.WriteLine (lineNumber);    //6
    }
}

上面代码在调用的时候,编译器使用了调用方信息,将它们替换成了参数值, 然后把代码编译成:

static void Main() => Foo ("Main", @"c:\source\test\Program.cs", 6);

Caller Info Attributes对logging很有用,特别当某个属性改变时,需要通知, 实际上.NET里面有个标准接口INotifyPropertyChanged,使用这个很有用:

// 标准接口,里面一个 event委托
public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

// 委托类型
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);

// 自定义EventArgs 
public class PropertyChangedEventArgs : EventArgs
{
    public PropertyChangedEventArgs (string propertyName);

    public virtual string PropertyName { get; }
}

使用:

public class Foo : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    // 主动invoke Delegate的方法
    void RaisePropertyChanged ([CallerMemberName] string propertyName = null)
    {
        PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
    }
    string customerName;
    public string CustomerName
    {
        get { return customerName; }
        set
        {
            if (value == customerName) return;
            customerName = value;
            RaisePropertyChanged();
            // The compiler converts the above line to:
            // RaisePropertyChanged ("CustomerName");
        }
    }
}

上面的代码,对可选参数propertyName使用了[CallerMemberName], 所以在调用的时候,不需要传递调用放的名字,编译器会自动写入。

4. Dynamic Binding

dynamic binding会将 编译时做的 binding,延迟到运行时。通常是在和动态语言交互的时候使用。

dynamic d = GetSomeObject();
d.Quack();

4.1 Static Binding Versus Dynamic Binding

a. 静态绑定
Duck d = ...
d.Quack();

对于上面的代码,Compiler的搜索顺序:

  1. Duck类中的无参方法Quack
  2. Duck类中的 带有可选参数的 方法Quack
  3. Duck基类的方法
  4. Duck的扩展方法
  5. 以上都没有,报告出编译错误

编译器根据 调用者类型和参数类型来编译,这叫做静态绑定

对于下面这种:

object d = ...
d.Quack();

会有编译错误,因为编译器只知道它的类型(object),而这个类型中找不到方法 Quack

b. 动态绑定
dynamic d = ...
d.Quack();

编译器在编译时不知道它的类型,需要在运行时再进行绑定类型,编译器仅仅把这个表达式打包。

在运行时,如果 动态对象实现了 IDynamicMetaObjectProvider, 那么这个接口就会用来完成绑定; 否则的话就会像compiler知道它的类型一样操作。 这2种分别叫做custom bindinglanguage binding. COM interop可以被视为第三种 dynamic binding

4.2 Custom Binding

custom binding发生在 动态对象实现了IDynamicMetaObjectProvider (IDMOP)接口时。虽然为自己定义的类实现这个接口,但是更多的情况下是我们从 on the DLR .NET实现了的动态语言中获取这么一个对象,比如从 IronPython, IronRuby

自定义使用的例子:

using System;
using System.Dynamic;
public class Test
{
    static void Main()
    {
        dynamic d = new Duck();
        d.Quack(); // Quack method was called
        d.Waddle(); // Waddle method was called
    }
}

public class Duck : DynamicObject
{
    public override bool TryInvokeMember (InvokeMemberBinder binder, object[] args, out object result)
    {
        Console.WriteLine (binder.Name + " method was called");
        result = null;
        return true;
    }
}

4.3 Language Binding

Language binding is useful when working around imperfectly designed types or inherent limitations in the .NET type system

static dynamic Mean (dynamic x, dynamic y) => (x + y) / 2;
static void Main()
{
    int x = 3, y = 4;
    Console.WriteLine (Mean (x, y));
}

根据设计,language runtime binding尽可能的和静态绑定工作一样,就像在运行时确认了它的类型。也就是说上面的代码,如果把dynamic换成int, 他们行为完全相同。

Dynamic binding越过了静态检查(静态类型安全性),但是它无法绕过动态类型安全性,它不同于反射,可以完全绕过类型安全。

4.4 RuntimeBinderException

动态绑定失败时会抛出RuntimeBinderException

4.5 Runtime Representation of Dynamic

运行时, dynamicobject类型有非常强的相似度,下面代码:

typeof (dynamic) == typeof (object)   // true
typeof (List<dynamic>) == typeof (List<object>)   //true
typeof (dynamic[]) == typeof (object[])       //true

就像object一样,dynamic对象也可以指向任何类型(非pointer):

dynamic x = "hello";
Console.WriteLine (x.GetType().Name); // String

x = 123; // No error (despite same variable)
Console.WriteLine (x.GetType().Name); // Int32

从结构上来说dynamicobject都是指向对象的引用,可以任意使用它们所指向对象的内容。

通过反射查看dynamic字段,它们被表示成了具有Attributeobject:

public class Test
{
    public dynamic Foo;
}
//is equivalent to:

public class Test
{
    [System.Runtime.CompilerServices.DynamicAttribute]
    public object Foo;
}

这样能让一般使用者知道这是 动态类型,同时也能让不支持动态绑定的语言像使用object一样正常工作。

4.5 Dynamic Conversions

运行时类型,就像编译时类型一样,只要类型兼容,就能转换

int i = 7;
dynamic d = i;
long j = d; // No cast required (implicit conversion)

int i = 7;
dynamic d = i;
short j = d; // throws RuntimeBinderException

4.6 var vs. dynamic

  • var says, “Let the compiler figure out the type.”
  • dynamic says, “Let the runtime figure out the type.”
dynamic x = "hello"; // 静态类型为dynamic,运行时类型为 string
var y = "hello"; //  静态类型为string,运行时类型为 string
int i = x; //  运行时错误,运行时类型不兼容
int j = y; // 编译时错误,compile-time error (cannot convert string to int)

dynamic x = "hello";
var y = x; // Static type of y is dynamic
int z = y; // Runtime error (cannot convert string to int)

4.7 Dynamic Expression

包含动态类型的表达式,整体都是动态类型的,因为他们整体类型信息,都被延迟到了 运行时才知道。使用错误会在运行时抛出异常。

但是关于包含动态类型的表达式的规则,也有一些例外

1.将动态类型转换成静态类型,会产生静态类型表达式(接收compile-time 检查):

dynamic x = 2;
var y = (int)x; // Static type of y is int

2.调用构造器总是会产生静态类型表达式

dynamic capacity = 10;
var x = new System.Text.StringBuilder (capacity) ;  // 静态类型表达式,dynamic类型发生了转换

3.其他情况,比如传入索引,使用delegate,都会变成 静态类型表达式

4.8 Dynamic Calls Without Dynamic Receivers

通常来说,使用dynamic的时候,总会涉及dynamic reciever,这个相当于调用者:

dynamic x = ...;
x.Foo(); // x is the receiver

但是当没有这个reciever的时候,将dynamic对象作为参数传入 以静态类型作为参数的方法时,具体调用哪个方法同样也是在运行时,根据这个动态对象的类型决定的:

class Program
{
    static void Foo (int x) { Console.WriteLine ("1"); }
    static void Foo (string x) { Console.WriteLine ("2"); }

    static void Main()
    {
        dynamic x = 5;
        dynamic y = "watermelon";
        Foo (x); // 1
        Foo (y); // 2
        Foo (x, x); // Compiler error - wrong number of parameters
        Fook (x); // Compiler error - no such method name
    }
}

而且当没有dynamic reciever的时候,调用 参数数量不正确,以及不存在的函数,那么编译时就会报错。

4.9 静态类型参与动态表达式绑定

在动态绑定的时候,静态类型信息也会被使用:

static void Foo (object x, object y) { Console.WriteLine ("oo"); }
static void Foo (object x, string y) { Console.WriteLine ("os"); }
static void Foo (string x, object y) { Console.WriteLine ("so"); }
static void Foo (string x, string y) { Console.WriteLine ("ss"); }

static void Main()
{
    object o = "hello";
    dynamic d = "goodbye";
    Foo (o, d);               // os
}
// 虽然d的类型是运行时知道的,但是 o的类型之前已经确定了
// 所以动态绑定,会在前2个之间选,最后根据y的类型,选择第二个

4.10 无法动态调用的函数 Uncallable Function

  • 扩展方法
  • 接口的成员,除非 先类型转换成该接口
  • 隐藏的(new)基类成员

理解
动态绑定是根据2部分信息来确定调用的:

  1. 调用的函数名
  2. 调用这个函数对象的 动态类型

然而上面3种无法动态调用的情况,都引入了额外的类型信息

a.扩展方法

扩展方法的使用实际上是编译器的行为, 编译器在编译的时候,根据using指定的命名空间,来搜索扩展方法,编译之后,这个using的概念已经不复存在了。所以运行时无法获取这个包含 扩展方法的类就获取不到了。

b.接口

当调用接口时,也是通过 implicit或者是explicit类型转换,来使用到了这个接口的成员。

interface IFoo { void Test(); }
class Foo : IFoo { void IFoo.Test() {} }

IFoo f = new Foo(); // Implicit cast to interface
f.Test();    // 通过接口调用

IFoo f = new Foo();
dynamic d = f;
d.Test(); // Exception thrown

上面这个动态类型d,在运行时,丢失了关于IFoo这个类型的信息,所以DLR无法完成动态绑定,它访问不到这个对象(只能获取到 实例对象成员):

Console.WriteLine (f.GetType().Name); // Foo
c. 使用hidden base member

也是通过类型转换或者 base来引入了这一个 additional type, 而这个type的信息,在运行时就丢失了。

Dynamic 部分小结

总的来说,动态绑定就像静态绑定一样,只不过是时间延迟到了运行时。 可以理解为一个对象它有2个类型,一个是静态类型,一个是动态类型。 编译器对静态类型已知的类可以进行编译处理,保证静态类型安全。而对不可知的,则延迟到了运行时执行。 在运行时,可以想象有另一个”编译器”来完成 静态时的操作。

5. 运算符重载(Overloading)

一般用来实现对 自定义结构体的操作符扩展。下面这些操作符可以被重载:

+ (unary) - (unary) ! ˜ ++
-- + - * /
% & | ^ <<
>> == != > <
>= <=

下面这些操作符也可以被重载

  1. implicit和explicit类型转换
  2. truefalse操作符(不是字面值)

下面这些可以被间接的重载:

  1. 复合赋值语句(+=, /= 等等), 通过重载 +,/等等
  2. && and || 通过重载 &和 |

5.1 Operator Functions

定义运算符函数:

  1. 函数名通过 operator关键字跟着一个 操作符符号 来指定
  2. 操作符函数必须是 public static
  3. 操作符函数的参数代表了操作数
  4. 返回值代表了表达式结果
  5. 至少有一个操作数是 这个操作符函数所在的类型
public struct Note
{
    int value;
    public Note (int semitonesFromA) { value = semitonesFromA; }

    // 操作符重载
    public static Note operator + (Note x, int semitones)
    {
        return new Note (x.value + semitones);
    }
    //public static Note operator + (Note x, int semitones) => new Note (x.value + semitones);
}

5.2 重载 比较操作符

相等和比较操作符经常在定义 struct的时候重载,有时候也会在定义class的时候使用。

定义相等和比较操作符的时候有一些严格的规则

  1. Pairing: C#编译器要求一组操作符需要一起重载: (== 和 !=) , (< 和 >), (<= 和 =>)
  2. Equals and GetHashCode: 大多数情况下,如果重载了==!=,C#编译器会要你也重载GetHashCodeEquals方法
  3. IComparable and IComparable<T>: 如果重载了< ><= =>,那么就应该实现这两个接口

5.3 Custom Implicit and Explicit Conversions

下面是implicit和explicit转换的重载:

public static implicit operator double (Note x) => 440 * Math.Pow (2, (double) x.value / 12 );


// Convert from hertz (accurate to the nearest semitone)
public static explicit operator Note (double x) => new Note ((int) (0.5 + 12 * (Math.Log (x/440) / Math.Log(2) ) ));

Note n = (Note)554.37; // explicit conversion
double x = n; // implicit conversion

其实对于weakly related types,下面这种方式更好:

  1. 写一个构造器,包含从某个类型转换 的参数
  2. 写一个 ToXXX 方法和 静态的FromXXX方法来完成转换

自定义转换,无法使用 asis

5.4 重载true和false操作符

这种使用情况极少数, 重载false和true操作符,意思是能够使用这个对象本身来进行 逻辑判断:

例子System.Data.SqlTypes.SqlBoolean结构体:

SqlBoolean a = SqlBoolean.Null;
if (a)
Console.WriteLine ("True");
else if (!a)
Console.WriteLine ("False");
else
Console.WriteLine ("Null");

写法:

public struct SqlBoolean
{
    private byte m_value;

    public static readonly SqlBoolean Null = new SqlBoolean(0);
    public static readonly SqlBoolean False = new SqlBoolean(1);
    public static readonly SqlBoolean True = new SqlBoolean(2);

    //构造器
    private SqlBoolean (byte value) { m_value = value; }

    public static bool operator true (SqlBoolean x)  => x.m_value == True.m_value;
    public static bool operator false (SqlBoolean x) => x.m_value == False.m_value;
    public static SqlBoolean operator ! (SqlBoolean x)
    {
        if (x.m_value == Null.m_value) return Null;
        if (x.m_value == False.m_value) return True;
        return False;
    }


}

6. Unsafe Code and Pointers (暂空)

7. Preprocessor Directives (暂空)

Preprocessor directives 告诉了编译器更多的关于 这个区域代码的信息,比如下面:

#define DEBUG
class MyClass
{
    int x;

    void Foo()
    {
        #if DEBUG
        Console.WriteLine ("Testing: x = {0}", x);
        #endif
    }
    ...
}

上面这个代码,Foo中的语句是否编译,取决于 DEBUG这个符号是否存在,如果把第一个符号定义移除,那么Foo中的代码就不会被编译。

预处理符号(preprocessor symbols) 可以被定义在一个单独的 源文件中,然后通过制定 /define:symbol命令行参数来传给编译器。

Preprocessor directiveAction
#define symbolDefines symbol
#undef symbolUndefines symbol
#if symbol [operator symbol2]...symbol to test
operators are ==, !=, &&, and
#elseExecutes code to subsequent #endif
#elif symbol [operator symbol2]Combines #else branch and #if test
#endifEnds conditional directives
#warning texttext of the warning to appear in compiler output
#error texttext of the error to appear in compiler output
#pragma warning [disable | restore]Disables/restores compiler warning(s)
#line [ number ["file"] |hidden]number specifies the line in source code; file is the filename to appear in computer output; hidden instructs debuggers to skip over code from this point until the next #line directive
#region nameMarks the beginning of an outline
#endregionEnds an outline region

7.1 Conditional Attributes

标有ConditionalAttribute的部分,仅仅当 特点的 preprocessor symbol存在时才会被编译:

// file1.cs
#define DEBUG
using System;
using System.Diagnostics;
[Conditional("DEBUG")]
public class TestAttribute : Attribute {}
// file2.cs
#define DEBUG
[Test]
class Foo
{
[Test]
string s;
}

7.2 Pragma Warning

编译器有时候会产生一些warning,warning作用是好的,但是有时候产生的warning并不是真的有用。所以你可以选择压制一些自己确定没有问题的warning,来把关注点减小到那些有用的warning上。

下面代码使用#pragma warning directive.让compiler不要警告所有关于Message字段的消息。

public class Foo
{
    static void Main() { }

    #pragma warning disable 414
    static string Message = "Hello";
    #pragma warning restore 414
}

Omitting the number in the #pragma warning directive disables or restores all warning codes.

8. XML Document

文档注释是用来注释类型或者成员的,以 三个 /开头:

/// <summary>Cancels a running query.</summary>
public void Cancel() { ... }

/// <summary>
/// Cancels a running query
/// </summary>
public void Cancel() { ... }

/**
<summary> Cancels a running query. </summary>
*/
public void Cancel() { ... }

当编译的时候使用/doc指示,那么编译器就会把这些文档注释拼接在一个XML文件中,有2个作用:

  1. 当这个XML文档在编译后程序集的同一个文件夹时,VS可以读取这些XML文件,来提示IntelliSense
  2. 第三方工具可以把它们变成HTML形式文档

8.1 Standard XML Documentation Tags

  • <summary>

    <summary>...</summary>
    Indicates the tool tip that IntelliSense should display for the type or member; typically a single phrase or sentence.

  • <remarks>

    <remarks>...</remarks>
    Additional text that describes the type or member. Documentation generatorspick this up and merge it into the bulk of a type or member’s description.

  • <param>

    <param name="name">...</param>
    Explains a parameter on a method.

  • <returns>

    <returns>...</returns>
    Explains the return value for a method.

  • <exception>

    <exception [cref="type"]>...</exception>
    Lists an exception that a method may throw (cref refers to the exception type).

  • <permission>

    <permission [cref="type"]>...</permission>
    Indicates an IPermission type required by the documented type or member.

  • <example>

    <example>...</example>
    Denotes an example (used by documentation generators). This usually contains both description text and source code (source code is typically within a<c> or <code>tag).

  • <c>

    <c>...</c>
    Indicates an inline code snippet. This tag is usually used inside an <example>block.

  • <code>

    <code>...</code>
    Indicates a multiline code sample. This tag is usually used inside an <example> block.

  • <see>

    <see cref="member">...</see>
    Inserts an inline cross-reference to another type or member. HTML documentation generators typically convert this to a hyperlink. The compiler emits a warning if the type or member name is invalid. To refer to generic types, use curly braces; for example, cref=”Foo{T,U}”.

  • <seealso>


    Cross-references another type or member. Documentation generators typically write this into a separate “See Also” section at the bottom of the page.

  • <paramref>

    <paramref name="name"/>
    References a parameter from within a or tag.

  • <list>

    <list type=[ bullet | number | table ]>
    <listheader>
    <term>...</term>
    <description>...</description>
    </listheader>
    <item>
    <term>...</term>
    <description>...</description>
    </item>
    </list>
    Instructs documentation generators to emit a bulleted, numbered, or tablestyle list.

  • <para>

    <para>...</para>
    Instructs documentation generators to format the contents into a separate paragraph.

  • <include>

    <include file='filename' path='tagpath[@name="id"]'>...</include>
    Merges an external XML file that contains documentation. The path attribute denotes an XPath query to a specific element in that file.

8.2 Type or Member Cross-References

XML type prefixID prefixes applied to…
NNamespace
TType (class, struct, enum, interface, delegate)
FField
PProperty (includes indexers)
MMethod (includes special methods)
EEvent
!Error
// Namespaces do not have independent signatures
namespace NS
{
/// T:NS.MyClass
class MyClass
{
/// F:NS.MyClass.aField
string aField;
/// P:NS.MyClass.aProperty
short aProperty {get {...} set {...}}
/// T:NS.MyClass.NestedType
class NestedType {...};
/// M:NS.MyClass.X()
void X() {...}
/// M:NS.MyClass.Y(System.Int32,System.Double@,System.Decimal@)
void Y(int p1, ref double p2, out decimal p3) {...}
/// M:NS.MyClass.Z(System.Char[ ],System.Single[0:,0:])
void Z(char[ ] p1, float[,] p2) {...}
/// M:NS.MyClass.op_Addition(NS.MyClass,NS.MyClass)
public static MyClass operator+(MyClass c1, MyClass c2) {...}
/// M:NS.MyClass.op_Implicit(NS.MyClass)˜System.Int32
public static implicit operator int(MyClass c) {...}
/// M:NS.MyClass.#ctor
MyClass() {...}
/// M:NS.MyClass.Finalize
˜MyClass() {...}
/// M:NS.MyClass.#cctor
static MyClass() {...}
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值