方法

作者:追风剑情 发布于:2021-4-25 15:20 分类:C#

示例——无参构造器
public class SomeType {
}

//等价于
public class SomeType {
	public SomeType() : base() { }
}

示例:字段初始化

using System;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            SomeType st = new SomeType("ctor...");
            Console.WriteLine(st.ToString());
            Console.Read();
        }
    }

    internal sealed class SomeType
    {
        // 如果显示初始化这些字段的值,
        // 编译器会将字段的初始化内嵌到每个构造器函数中,
        // 这就会造成代码膨胀

        // 不要显式初始化下面的字段
        private Int32 m_x;
        private String m_s;
        private Double m_d;
        private Byte m_b;

        // 该构造器将所有字段设为默认值
        // 其他所有构造器显式调用该构造器
        public SomeType()
        {
            m_x = 5;
            m_s = "Hi there";
            m_d = 3.14159;
            m_b = 0xff;
        }

        // 该构造器将所有的字段都设为默认值,然后修改m_s
        public SomeType(String s) : this()
        {
            m_s = s;
        }

        // 该构造器首先将所有字段都设为默认值,然后修改m_x和m_s
        public SomeType(Int32 x, String s) : this()
        {
            m_x = x;
            m_s = s;
        }

        public override string ToString()
        {
            return string.Format("m_x={0},m_s={1},m_d={2},m_b={3}",
                m_x,m_s,m_d,m_b);
        }
    }
}

运行测试
11111.png

示例——值类型(struct)构造器
internal struct Point
{
	//值类型的字段总是被编译器初始化为0或null
        //不能为实例字段直接赋值
	public Int32 m_x, m_y;
        //可以为静态字段直接赋值
        public static Int32 m_z = 5;

	//错误: CS0568 结构不能包含显式的无参数构造函数
	//因为编译器不会自动调用无参构造器
	//public Point() { }

	public Point(Int32 x, Int32 y)
	{
		//必须对所有字段完成赋值,否则会报错
		m_x = x;
		m_y = y;
	}

	public Point(Int32 x)
	{
		//在值类型(struct)中this是可读写的,在引用类型(class)中this是只读的
		//看起来很奇怪,但编译没问题,会将所有字段初始化为0或null
		this = new Point();
		//现在可以只对部分字段赋值了
		m_x = x;//用x覆盖m_x的0
	}
}

internal sealed class Rectangle
{
	public Point m_topLeft, m_bottomRight;

	public Rectangle()
	{
		//在C#中,向一个值类型应用关键字new,
		//可以调用构造器来初始化值类型的字段
		m_topLeft = new Point(1, 2);
		m_bottomRight = new Point(100, 200);
	}
}

示例——静态构造器
internal sealed class SomeRefType
{
	//下面四种叫法都指同一种构造器:
	//类型构造器(type constructor)
	//静态构造器(static constructor)
	//类构造器(class constructor)
	//类型初始化器(type initializer)
	static SomeRefType()
	{
		//SomeRefType被首次访问时,执行这里的代码
	}
}

internal struct SomeValType
{
	//C#允许值类型定义无参的类型构造器
	static SomeValType()
	{
		//SomeValType被首次访问时,执行这里的代码
	}
}

类型构造器总是私有;C#自动把它们标记为private。之所以必须私有,是为了防止任何由开发人员写的代码调用它,对它的调用总是由CLR负责。

重要提示   虽然能在值类型中定义类型构造器,但永远不要真的那么做,因为CLR有时不会调用值类型的静态类型构造器。

示例——静态构造器
internal struct SomeValType {
	static SomeValType() {
		Console.WriteLine("这句话永远不会显示");
	}
	public Int32 m_x;
}

public sealed class Program {
	public static void Main() {
		SomeValType[] a = new SomeValType[10];
		a[0].m_x = 123;
		Console.WriteLine(a[0].m_x);//显示123
	}
}

示例——静态构造器
internal struct SomeValType {
	private static Int32 s_x = 5;
}

//等效于下面的写法
internal struct SomeValType {
	private static Int32 s_x;
	static SomeValType() {
		s_x = 5;
	}
	
}

注意   由于CLR保证了一个类型构造器在每个AppDomain中只执行一次,而且(这种执行)是线程安全的,所以非常适合在类型构造器中初始化类型需要的任何单实例(Singleton)对象。

单个线程中的两个静态类型构造器包含相互引用的代码可能出问题

操作符重载方法

C#允许重载一元和二元操作符,以及由编译器生成的对应的CLS(Common Language Specification,公共语言规范)方法名。

C#的一元操作符及其相容于CLS的方法名
C#操作符 特殊方法名 推荐的相容于CLS的方法名
+ op_UnaryPlus Plus
- op_UnaryNegation Negate
! op_LogicalNot Not
~ op_OnesComplement OnesComplement
++ op_Increment Increment
-- op_Decrement Decrement
(无) op_True IsTrue{get;}
(无) op_False IsFalse{get;}

C#的二元操作符及其相容于CLS的方法名
C#操作符 特殊方法名 推荐的相容于CLS的方法名
+     op_Addition Add
- op_Subtraction Subtract
* op_Multiply Multiply
/ op_Division Divide
% op_Modulus Mod
& op_BitwiseAnd BitwiseAnd
| op_BitwiseOr BitwiseOr
^ op_ExclusiveOr Xor
<< op_LeftShift
LeftShift
>> op_RightShift RightShift
== op_Equality Equals
!= op_Inequality Equals
< op_LessThan Compare
> op_GreaterThan Compare
<= op_LessThanOrEqual Compare
>= op_GreaterThanOrEqual Compare

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            //隐式转型
            Rational r1 = 5;//Int32转Rational
            Rational r2 = 2.5F;//Single转Rational

            //显式转型
            Int32 x = (Int32)r1;//Ration转Int32
            Single s = (Single)r2;//Ration转Single
        }
    }

    public sealed class Rational
    {
        private Int32 i;
        private Single si;

        // 由一个Int32构造一个Rational
        public Rational(Int32 num) { i = num; }
        // 由一个Single构造一个Rational
        public Rational(Single num) { si = num; }
        // 将一个Rational转换成一个Int32
        public Int32 ToInt32() { return i; }
        // 将一个Rational转换成一个Single
        public Single ToSingle() { return si; }
        // 由一个Int32隐式构造并返回一个Rational
        // 被编译成:
        // public static Rational op_Implicit(Int32 num)
        public static implicit operator Rational(Int32 num)
        {
            return new Rational(num);
        }
        // 由一个Single隐式构造并返回一个Rational
        // 被编译成:
        // public static Rational op_Implicit(Single num)
        public static implicit operator Rational(Single num)
        {
            return new Rational(num);
        }
        // 由一个Rational显式返回一个Int32
        // 被编译成:
        // public static Int32 op_Explicit(Rational r)
        public static explicit operator Int32(Rational r)
        {
            return r.ToInt32();
        }
        // 由一个Rational显式返回一个Single
        // 被编译成:
        // public static Single op_Explicit(Rational r)
        public static explicit operator Single(Rational r)
        {
            return r.ToSingle();
        }
    }
}
注意   只有在转换不损失精度或数量级的前提下才能定义隐式转换操作符。反之,则应该定义显式转换操作符。显式转换失败,应该让显式转换操作符抛出OverflowException或者InvalidOperationException异常。

为了真正理解操作符重载方法和转换操作符方法,强烈建议将 System.Decimal 类型作为典型来研究。

扩展方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp5
{
    class Program
    {
        static void Main(string[] args)
        {
            StringBuilder sb = new StringBuilder("Hello. My name is Jeff.");
            Int32 index = sb.Replace('.', '!').IndexOf('!');
        }
    }

    public static class StringBuilderExtensions
    {
        // 为StringBuilder定义扩展方法IndexOf
        public static Int32 IndexOf(this StringBuilder sb, Char value)
        {
            if (null == sb)//注意: sb可能为null
                return -1;
            for (Int32 index = 0; index < sb.Length; index++)
                if (sb[index] == value) return index;
            return -1;
        }
    }
}


规则和原则:
● C#只支持扩展方法,不支持扩展发展、扩展事件、扩展操作符等。
● 扩展方法必须在非泛型的静态类中声明。
● 扩展方法类不能为嵌套类
● 如果扩展方法类在某个命名空间中,程序员必须导入(using 扩展类所在命名空间)才能使用
● 派生类也会得到扩展方法
● 如果微软在类中增加了和扩展方法相同的方法,我们定义的扩展方法将失效

为接口类型定义扩展方法

using System;
using System.Collections.Generic;

namespace ConsoleApp5
{
    class Program
    {
        static void Main(string[] args)
        {
            //每个Char在控制台上单独显示一行
            "Grant".ShowItems();
            //每个String在控制台上单独显示一行
            new[] { "Jeff", "Kristin" }.ShowItems();
            //每个Int32在控制台上单独显示一行
            new List<Int32>() { 1, 2, 3 }.ShowItems();
        }
    }

    public static class IEnumerableExtensions
    {
        //任何表达式,只要它最终的类型实现了IEnumerable<T>接口,就能调用此扩展方法
        public static void ShowItems<T>(this IEnumerable<T> collection)
        {
            foreach (var item in collection)
                Console.WriteLine(item);
        }
    }
}


为委托类型定义扩展方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp6
{
    class Program
    {
        static void Main(string[] args)
        {
            //抛出NullReferenceException
            Action<Object> action = o => Console.WriteLine(o.GetType());
            //吞噬NullReferenceException
            action.InvokeAndCatch<NullReferenceException>(null);

            //C#编译器允许创建委托来引用一个对象上的扩展方法
            Action a = "Jeff".ShowItems;
            a();
        }
    }

    public static class DelegateExtensions
    {
        public static void InvokeAndCatch<TException>(this Action<Object> d, Object o)
            where TException : Exception
        {
            try { d(o); }
            catch (TException) { }
        }
    }

    public static class IEnumerableExtensions
    {
        //任何表达式,只要它最终的类型实现了IEnumerable<T>接口,就能调用此扩展方法
        public static void ShowItems<T>(this IEnumerable<T> collection)
        {
            foreach (var item in collection)
                Console.WriteLine(item);
        }
    }
}


给枚举添加扩展方法

C#编译器会对定义了扩展方法的静态类应用 ExtensionAttribute 特性。

分部方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp6
{
    class Program
    {
        static void Main(string[] args)
        {
            
        }
    }

    //工具生成的代码,存储在某个源代码文件中
    //这个技术可用于密封类、静态类以及值类型
    internal sealed partial class Base
    {
        private String m_name;

        //这是分部方法的声明
        partial void OnNameChanging(String value);

        public String Name
        {
            get { return m_name; }
            set
            {
                //如果OnNameChanging分部方法没有实现主体,这句调用不会产生性能开销
                OnNameChanging(value.ToUpper());
                m_name = value;
            }
        }
    }

    //开发人员生成的代码,存储在另一个源代码文件中
    internal sealed partial class Base
    {
        //这是分部方法的实现,会在m_name更改前调用
        partial void OnNameChanging(string value)
        {
            if (String.IsNullOrEmpty(value))
                throw new ArgumentNullException("value");
        }
    }
}


注意   分部方法的工作方式类似于 System.Diagnostics.ConditionalAttribute 特性。然而,分部方法只能在单个类型中使用,而 ConditionalAttribute 能用于对另一个类型中定义的方法进行有选择的调用。
参考:
C#中使用System.Diagnostics.ConditionalAttribute移除无用函数调用
ConditionalAttribute

规则和原则
● 它们只能在分部类或结构中声明。
● 分部方法的返回类型始终是 void,任何参数都不能用 out 修饰符来标记。之所以有这两个限制,是因为方法在运行时可能不存在,所以不能将变量初始化为方法也许会返回的东西。 类似地,不允许 out 参数是因为方法必须初始化它,而方法可能不存在。分部方法可以有 ref 参数,可以是泛型方法,可以是实例或静态方法,而且可以标记为 unsafe
● 当然,分部方法的声明和实现必须具有完全一致的签名。如果两者都应用了定制特性,编译器会合并两个方法的特性。应用于参数的任何特性也会合并。
● 如果没有对应的实现部分,便不能在代码中创建一个委托来引用这个分部方法。这同样是由于方法在运行时不存在。 ● 分部方法总是被视为 private 方法,但C#编译器禁止在分部方法声明之前添加 private 关键字。

Powered by emlog  蜀ICP备18021003号-1   sitemap

川公网安备 51019002001593号