读书笔记: C# 7.0 in a nutshell (第 四 章 Advanced C#- 下)
内容:
第四章(下): Advanced C#
- Tuple(C# 7.0)
- Atrribute
- Caller Info Attributes
- Dynamic Binding
- 运算符重载
- Unsafe code and Pointer
- Preprocessor Directives
- 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); // BobConsole.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类,positional和named,位置参数相当于 Attribute类型的公开构造器的参数,而命名参数相当于 public字段或者public属性。
2.3 Attribute Targets
Attribute有使用的Target,通常是它紧挨着的 Type或者Type成员(字段等)。同时,Attribute也可以运用在程序集上,这需要明确指定这个Attribute的Target。
示例: 使用CLSCompliant
Atrribute对整个程序集生效:
[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); //MainConsole.WriteLine (filePath); // c:\source\test\Program.csConsole.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的搜索顺序:
Duck
类中的无参方法Quack
Duck
类中的 带有可选参数的 方法Quack
Duck
基类的方法- 对
Duck
的扩展方法 - 以上都没有,报告出编译错误
编译器根据 调用者类型和参数类型来编译,这叫做静态绑定
对于下面这种:
object d = ...
d.Quack();
会有编译错误,因为编译器只知道它的类型(object
),而这个类型中找不到方法 Quack
b. 动态绑定
dynamic d = ...
d.Quack();
编译器在编译时不知道它的类型,需要在运行时再进行绑定类型,编译器仅仅把这个表达式打包。
在运行时,如果 动态对象实现了 IDynamicMetaObjectProvider
, 那么这个接口就会用来完成绑定; 否则的话就会像compiler知道它的类型一样操作。 这2种分别叫做custom binding和language 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 calledd.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
运行时, dynamic
和object
类型有非常强的相似度,下面代码:
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); // Stringx = 123; // No error (despite same variable)
Console.WriteLine (x.GetType().Name); // Int32
从结构上来说dynamic
和object
都是指向对象的引用,可以任意使用它们所指向对象的内容。
通过反射查看dynamic字段,它们被表示成了具有Attribute的object
:
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); // 1Foo (y); // 2Foo (x, x); // Compiler error - wrong number of parametersFook (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部分信息来确定调用的:
- 调用的函数名
- 调用这个函数对象的 动态类型
然而上面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) ! ˜ ++
-- + - * /
% & | ^ <<
>> == != > <
>= <=
下面这些操作符也可以被重载
- implicit和explicit类型转换
true
和false
操作符(不是字面值)
下面这些可以被间接的重载:
- 复合赋值语句(+=, /= 等等), 通过重载 +,/等等
- && and || 通过重载 &和 |
5.1 Operator Functions
定义运算符函数:
- 函数名通过
operator
关键字跟着一个 操作符符号 来指定 - 操作符函数必须是
public static
- 操作符函数的参数代表了操作数
- 返回值代表了表达式结果
- 至少有一个操作数是 这个操作符函数所在的类型
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的时候使用。
定义相等和比较操作符的时候有一些严格的规则:
- Pairing: C#编译器要求一组操作符需要一起重载: (== 和 !=) , (< 和 >), (<= 和 =>)
- Equals and GetHashCode: 大多数情况下,如果重载了
==
和!=
,C#编译器会要你也重载GetHashCode
和Equals
方法 IComparable
andIComparable<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,下面这种方式更好:
- 写一个构造器,包含从某个类型转换 的参数
- 写一个
ToXXX
方法和 静态的FromXXX
方法来完成转换
自定义转换,无法使用
as
和is
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 DEBUGConsole.WriteLine ("Testing: x = {0}", x);#endif}...
}
上面这个代码,Foo
中的语句是否编译,取决于 DEBUG
这个符号是否存在,如果把第一个符号定义移除,那么Foo
中的代码就不会被编译。
预处理符号(preprocessor symbols) 可以被定义在一个单独的 源文件中,然后通过制定 /define:symbol
命令行参数来传给编译器。
Preprocessor directive | Action |
---|---|
#define symbol
|
Defines symbol |
#undef symbol
|
Undefines symbol |
#if symbol [operator symbol2]...
|
symbol to test operators are ==, !=, &&, and |
#else
|
Executes code to subsequent #endif |
#elif symbol [operator symbol2]
|
Combines #else branch and #if test |
#endif
|
Ends conditional directives |
#warning text
|
text of the warning to appear in compiler output |
#error text
|
text 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 name
|
Marks the beginning of an outline |
#endregion
|
Ends an outline region |
7.1 Conditional Attributes
标有Conditional
Attribute的部分,仅仅当 特点的 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 414static 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个作用:
- 当这个XML文档在编译后程序集的同一个文件夹时,VS可以读取这些XML文件,来提示IntelliSense
- 第三方工具可以把它们变成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 prefix | ID prefixes applied to… |
---|---|
N | Namespace |
T | Type (class, struct, enum, interface, delegate) |
F | Field |
P | Property (includes indexers) |
M | Method (includes special methods) |
E | Event |
! | 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() {...}
}
}
读书笔记: C# 7.0 in a nutshell (第 四 章 Advanced C#- 下)相关推荐
- 读书笔记,《刻意练习》,第四章,黄金标准
有目的的练习虽然很有效,但是他忽略了一些东西.除了简单的集中注意力,并且迫使人们走出舒适区,其实还有一些其他的事情值得我们注意. 比如在前面的记忆训练当中,史蒂夫.法龙能够记住82个数字,而雷尼尽 ...
- 精通Web Analytics 2.0 (6) 第四章:点击流分析的奇妙世界:实际的解决方案
精通Web Analytics 2.0 (6) 第四章:点击流分析的奇妙世界:实际的解决方案 精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第四章:点击流分析的奇妙世界:实际 ...
- 【正点原子Linux连载】第四十四章 设备树下的LED驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...
- [读书笔记]《算法竞赛入门经典》第1章
书名:算法竞赛-入门经典 第2版 作者:刘汝佳 类别:读书笔记 文章目录 前言 第1章 程序设计入门 1.1 算术表达式 1.2 变量及其输入 1.3 顺序结构程序设计(Sequential Prog ...
- 读书笔记 计算机系统--系统架构与操作系统的高度集成 第一章概叙
大二下开始意识到计算机基础的重要性,就去图书馆随缘找了一本机械工业出版社的大部头书籍开始看, 也为了让自己养成总结和记录的习惯,把每一章的内容结构化的写成读书笔记.以后查阅方便. 这本书是集成的,计算 ...
- Box2D v2.3.0 用户指南(第四章)
第四章 碰撞模块(Collision Module) 4.1简介 碰撞模块包含形状(shape)以及操作它们的函数.此外,碰撞模块还包括dynamictree和broad-phase来加快大型系 ...
- 读书笔记《从0到1--开启商业与未来的秘密》
从0到1--开启商业与未来的秘密 Zero to One--Notes on Startups,Or How to Build The Future [美]Peter Thiel,Blake Mast ...
- 读书笔记之《C语言深度剖析》第一章:关键字
第一章引言 什么是定义及声明? 定义:定义是编译器创建一个对象,并且为这个对象分配一块内存并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名. 声明:1.告诉编译器该变量名已经匹配了一个内存 ...
- 读书笔记《数据挖掘概念与技术》第2章 数据预处理 2.4 数据集成和变换
<数据挖掘:概念与技术(原书第2版)> 2.4 数据集成和变换 数据挖掘经常需要数据集成-合并来自多个数据存储的数据.数据还可能需要转换成适于挖掘的形式.本节介绍数据集成和数据变换. 2. ...
- python基础学习[python编程从入门到实践读书笔记(连载六)]:数据可视化项目第17章
文章目录 使用API end 项目结果: 使用plotly可视化github最受欢迎的python仓库: 修改后的可视化图表: 使用API 编写独立的程序,实现对获取的数据可视化.我们使用Web AP ...
最新文章
- 优秀程序员应该做的几件事【转】
- 首个镜子分割网络问世,大连理工、鹏城实验室、香港城大出品 | ICCV 2019
- Android的事件分发
- P2611-[ZJOI2012]小蓝的好友【Treap,扫描线】
- Spring Data Solr教程:向所有存储库添加自定义方法
- flutter 如何判断在哪个页面_Agora 教程:构建你的第一个 Flutter 视频通话应用
- oracle怎么判断地址相似,如何查看oracle数据文件的地址
- SAP License:SAP精细化的应收付及要素为根设计思想
- Bailian3256 矩阵的乘法【数学计算】
- linux移植简介[MS2]
- dnn神经网络_Facebook AI新研究:可解释神经元或许会阻碍DNN的学习
- c语言程序设计实验第二版答案,C语言程序设计实验指导及习题答案
- pyshp读写shapefile
- 基于JAVA社团管理系统计算机毕业设计源码+数据库+lw文档+系统+部署
- 一个性价比超高的英语口语平台
- pandas 数据读取与保存
- 诚之和:各业务不断爆出裁员,字节跳动如何“过冬”?
- 【车载】度(角度)和弧度的概念
- 7. 伪随机数的生成
- android手机或者平板重力感应器描述