参数和属性

参数和属性

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[...] 作为表达索引器的语法。

属性本质上是方法,而 c# 和CLR 允许泛型方法,但C# 不允许属性有自己的泛型类型参数。

原文地址:https://www.cnblogs.com/mingjie-c/p/11650359.html