CLR默认所有方法参数都传值。
-
传递引用类型的对象时,对象引用被传递给方法,这意味着方法能修改对象,而调用者能看到这些修改。
-
对于值类型的实例,传给方法的是实例的一个副本。
C#用关键字out和ref
-
out表明调用方法前,参数可以不初始化;
-
ref表明调用方法前,参数必须初始化完成;
-
为值类型使用out和ref,效果等同于以传值的方式传递引用类型。
public sealed class Program { public static void Main() { Int32 x; // x is uninitialized. GetVal(out x); // x doesn’t have to be initialized. Console.WriteLine(x); // Displays "10" } private static void GetVal(out Int32 v) { v = 10; // This method must initialize v. } } public sealed class Program { public static void Main() { Int32 x = 5; // x is initialized. AddVal(ref x); // x must be initialized. Console.WriteLine(x); // Displays "15" } private static void AddVal(ref Int32 v) { v += 10; // This method can use the initialized value in v. } }
向方法传递可变数量的参数
为了接收可变数量的参数,方法要像下面这样声明:
static Int32 Add(params Int32[] values) { // NOTE: it is possible to pass the 'values' // array to other methods if you want to. Int32 sum = 0; if (values != null) { for (Int32 x = 0; x < values.Length; x++) sum += values[x]; } return sum; }
要像下面这样调用该方法:
public static void Main() { // Displays "15" Console.WriteLine(Add(new Int32[] { 1, 2, 3, 4, 5 } )); }
上述代码编译没有问题,并可以运行,但是有点丑。我们希望能像下面这样调用:
public static void Main() { // Displays "15" Console.WriteLine(Add(1, 2, 3, 4, 5)); }
让人兴奋的是这样做也没有问题,因为params关键字的存在,编译器向参数应用了定制特性System.ParamArrayAttribute的一个实例。
注意:
-
params只能应用于方法签名中的最后一个参数。
-
params参数只能标识一维数组(任意类型)。
参数和返回类型的设计规范
总之要保证方法有尽量大的灵活性,使用方法的适用范围更大。
-
声明方法的参数类型时,应尽量指定最弱的类型,宁愿要接口也不要基类;
// 好 Desired: This method uses a weak parameter type public void ManipulateItems<T>(IEnumerable<T> collection) { ... } // 不好 Undesired: This method uses a strong parameter type public void ManipulateItems<T>(List<T> collection) { ... }
-
对于方法的返回值类型最好声明为强类型(防止受限于特定类型);
// 好 Desired: This method uses a strong return type public FileStream OpenFile() { ... } // 不好 Undesired: This method uses a weak return type public Stream OpenFile() { ... } // Flexible: This method uses a weaker return type public IList<String> GetStringCollection() { ... } // Inflexible: This method uses a stronger return type public List<String> GetStringCollection() { ... }
这里是不是搞错了,应该是返回类型最好为弱类型才能更灵活??
属性
CLR支持两种属性:
-
无参属性:就是平常说的属性。
-
有参属性:C#将有参属性称为所引器。
属性这个概念的出现是为了解决类对象的封装,如果不考虑类的封装,直接访问字段就好了,但是这样做非常不安全。
我们可以将属性想象为智能字段,它相当于对属性进行了一个包装。属性使得对字段的访问更安全,同时又不像调用方法访问字段那样蹩脚,例如:
使用时要调用方法:
e.SetName("Jeffrey Richter"); // Updates the employee's name String EmployeeName = e.GetName(); // Retrieves the employee's name e.SetAge(48); // Updates the employee's age e.SetAge(-5); // Throws ArgumentOutOfRangeException Int32 EmployeeAge = e.GetAge(); // Retrieves the employee's age
下面的类使用了属性,它与前面定义的类功能相同:
在使用时更加方便:
e.Name = "Jeffrey Richter"; // "Sets" the employee name String EmployeeName = e.Name; // "Gets" the employee's name e.Age = 48; // "Sets" the employee's age e.Age = -5; // Throws ArgumentOutOfRangeException Int32 EmployeeAge = e.Age; // "Gets" the employee's age
CLR支持静态、实例、抽象、和虚属性。另外,属性可用任意“可访问性”修饰符来标记。而且可以在接口中定义。
每个属性都有名称和类型(类型不能为void)。
属性不能重载,即不能定义名称相同、类型不同的两个属性。
属性的get 和 set 方法不一定要访问支持字段。
声明属性而不提供 get/set 方法的实现,C#会自动为你声明一个私有字段。这是C#提供的一种简介的语法,称为自动实现的属性(AIP),例如下面的Name属性:
public sealed class Employee { // This property is an automatically implemented property public String Name { get; set; } private Int32 m_Age; public Int32 Age { get { return(m_Age); } set { if (value < 0) // The 'value' keyword always identifies the new value. throw new ArgumentOutOfRangeException("value", value.ToString(), "The value must be greater than or equal to 0"); m_Age = value; } } }
属性不能作为 out 或ref 参数传给方法,而字段可以。因为属性的根本是一个方法。
属性方法可能花较长时间执行,字段访问则总是立即完成。要线程同步不要使用属性,而要使用方法。
匿名类型:
利用C#的匿名类型功能,可以用很简洁的语法来自动声明不可变的元组类型。
// Define a type, construct an instance of it, & initialize its properties var o1 = new { Name = "Jeff", Year = 1964 }; // Display the properties on the console: Console.WriteLine("Name={0}, Year={1}", o1.Name, o1.Year);// Displays: Name=Jeff, Year=1964
编译器会推断每个表达式的类型,创建推断类型的私有字段,为每个字段创建公共只读属性,并创建一个构造器来接受所有这些表达式。在构造器的代码中,会用传给它的表达式的求值结果来初始化私有只读字段。最终编译器生成的类看起来像这样:
[CompilerGenerated] internal sealed class <>f__AnonymousType0<...>: Object { private readonly t1 f1; public t1 p1 { get { return f1; } } ... private readonly tn fn; public tn pn { get { return fn; } } public <>f__AnonymousType0<...>(t1 a1, ..., tn an) { f1 = a1; ...; fn = an; // Set all fields } public override Boolean Equals(Object value) { // Return false if any fields don't match; else true } public override Int32 GetHashCode() { // Returns a hash code generated from each fields' hash code } public override String ToString() { // Return comma•separated set of property name = value pairs } }
-
编译器会生成Equals 和 GetHashCode方法,因此匿名类型的实例能放到哈希表集合中。
-
属性是只读的,而不是可读可写,目的是防止对象的哈希码发生改变。
-
如果你在源代码中定义了多个匿名类型,而且这些类型具有相同的结构,那么它只会创建一个匿名类型定义,但创建该类型的多个实例。
-
由于这种类型的同一性,所以可以创建一个隐式类型的数组,在其中包含一组匿名类型的对象。
// This works because all of the objects are of the same anonymous type var people = new[] { o1, // From earlier in this section new { Name = "Kristin", Year = 1970 }, new { Name = "Aidan", Year = 2003 }, new { Name = "Grant", Year = 2008 } };
匿名类型经常用于LINQ 的使用中。用LINQ 执行查询,从而生成一组对象构成的集合,这些对象都是相同的匿名类型。
String myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); var query = from pathname in Directory.GetFiles(myDocuments) let LastWriteTime = File.GetLastWriteTime(pathname) where LastWriteTime > (DateTime.Now • TimeSpan.FromDays(7)) orderby LastWriteTime select new { Path = pathname, LastWriteTime };// Set of anonymous type objects foreach (var file in query) Console.WriteLine("LastWriteTime={0}, Path={1}", file.LastWriteTime, file.Path);
注意: 匿名类型的实例不能泄漏到方法外部。方法原型不能接受匿名类型的参数,因为无法指定匿名类型。类似地,方法也不能返回对匿名类型的引用。
System.Tuple 类型
和匿名类型相似,Tuple 创建好后就不可变了(所有属性都只读)。虽然在Tuple 类型定义中看不到其他的,但Tuple 类还提供了 CompareTo,Equals,GetHashCode 和ToString 方法以及Size 属性。
所有Tuple 类型都实现了IStructuralEquatable,IStructuralComparable 和 IComparable 接口,所以可以比较两个 Tuple对象,对它们的字段进行比较。
// Returns minimum in Item1 & maximum in Item2 private static Tuple<Int32, Int32> MinMax(Int32 a, Int32 b) { return new Tuple<Int32, Int32>(Math.Min(a, b), Math.Max(a, b)); } // This shows how to call the method and how to use the returned Tuple private static void TupleTypes() { var minmax = MinMax(6, 2); Console.WriteLine("Min={0}, Max={1}", minmax.Item1, minmax.Item2); // Min=2, Max=6 }
注意:Tuple 的生产者和消费者必须对 Item 属性返回的内容又一个清楚的理解。对于 Tuple 类型,属性一律被 Microsoft 称为 Item,编程者改不了。使用时要在自己的代码中添加详细说明每个Item 代表什么。
无参属性用起来就像是在访问字段。有参属性它的 Get 访问器方法接受一个或多个参数,Set 访问器方法接受两个或多个参数。C# 称有参属性为所引器。
C# 使用数组风格的语法来公开有参属性(索引器)。换句话说,可将所引器看成是C# 开发人员对 “ [ ] ” 操作符的重载。下面是一个示例 BitArray 类,它允许数组风格的语法来索引由该类的实例维护的一组二进制位。
using System; public sealed class BitArray { // Private array of bytes that hold the bits private Byte[] m_byteArray; private Int32 m_numBits; // Constructor that allocates the byte array and sets all bits to 0 public BitArray(Int32 numBits) { // Validate arguments first. if (numBits <= 0) throw new ArgumentOutOfRangeException("numBits must be > 0"); // Save the number of bits. m_numBits = numBits; // Allocate the bytes for the bit array. m_byteArray = new Byte[(numBits + 7) / 8]; } // This is the indexer (parameterful property). public Boolean this[Int32 bitPos] { // This is the indexer's get accessor method. get { // Validate arguments first if ((bitPos < 0) || (bitPos >= m_numBits)) throw new ArgumentOutOfRangeException("bitPos"); // Return the state of the indexed bit. return (m_byteArray[bitPos / 8] & (1 << (bitPos % 8))) != 0; } // This is the indexer's set accessor method. set { if ((bitPos < 0) || (bitPos >= m_numBits)) throw new ArgumentOutOfRangeException("bitPos", bitPos.ToString()); if (value) { // Turn the indexed bit on. m_byteArray[bitPos / 8] = (Byte) (m_byteArray[bitPos / 8] | (1 << (bitPos % 8))); } else { // Turn the indexed bit off. m_byteArray[bitPos / 8] = (Byte) (m_byteArray[bitPos / 8] & ~(1 << (bitPos % 8))); } } } }
索引器用起来很简单:
// Allocate a BitArray that can hold 14 bits. BitArray ba = new BitArray(14); // Turn all the even•numbered bits on by calling the set accessor. for (Int32 x = 0; x< 14; x++) { ba[x] = (x % 2 == 0); } // Show the state of all the bits by calling the get accessor. for (Int32 x = 0; x< 14; x++) { Console.WriteLine("Bit " + x + " is " + (ba[x]? "On" : "Off")); }
System.Collections.Generic.Dictionary 中就提供了类似的索引器,他获取一个键,并返回该键关联的值。
和无参属性不同,类型可提供多个重载的索引器。
C# 不支持定义静态索引器属性,只允许在对象的实例上定义索引器。可以使用this[...] 作为表达索引器的语法。