C# 小知识点记录

总结看书时的笔记

1. new的三种用法

    a. new运算符:用于创建对象和调用构造函数。

    b. new修饰符:在用于修饰符时,new可以显示隐藏从基类中继承的成员(调用该成员时,不会调用父类中的成员);而override是重写父类中的abstract/virtual方法;

    c. new约束:在泛型约束时,用于限制可能用于泛型声明中类型形式参数的时机参数的类型;与其他约束一起使用时,new()必须为最后一个;

msdn https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/new

2.==与equal

    ==用于值类型时,比较两个值类型是否相等;用于除string之外的引用类型时,判断两者引用是否同一个对象;对于string这个特殊的引用类型,==比较两个字符串是否相等;

    equal对于string类型来说,也是比较两个字符串是否相等;

    除string外的引用类型,equals比较values是否相等,而==比较object references是否相同(是否引用同一个对象);

    the ==operator compares object references(shallow comparision) whereas Equals compares object content(deep comparison)

参考:https://stackoverflow.com/questions/814878/c-sharp-difference-between-and-equals

3. 运算符重载

    运算符重载方法必须为public static

    若要在自定义类上重载运算符,需要在该类上创建具有正确签名的方法。 该方法必须命名为“operator X”,其中 X 是重载的运算符的名称或符号。 一元运算符具有一个参数,二元运算符具有两个参数。 在每种情况下,都必须有一个参数与声明运算符的类或结构的类型相同

    例如:

public static Complex operator +(Complex c1, Complex c2)
{
    return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
}

可重载运算符:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/statements-expressions-operators/overloadable-operators

4. 类型转换-转换操作符

    将一个自定义类型转换为其他类型(如string),需要定义对应方法,例如ToString()等,例如:

    public sealed class Rational
{
    public Rational(int num)
    {}
    
    //to int32
    public Int32 ToInt32()
    {...}
    //to string
    public string ToString()
    {...}

}

C#中也可以定义转换操作符重载:

    在implicit或explicit关键字后,要指定operator关键字告诉编译器该方法是一个转换操作符。在operator后,指定对象要转换成什么类型,在圆括号中,则指定要从什么类型转换。

4.1 显示转换运算符explicit

// Must be defined inside a class called Fahrenheit:
public static explicit operator Celsius(Fahrenheit fahr)
{
    return new Celsius((5.0f / 9.0f) * (fahr.degrees - 32));
}

通过如下的方式调用转换运算符:

Fahrenheit fahr = new Fahrenheit(100.0f);
Console.Write("{0} Fahrenheit", fahr.Degrees);
Celsius c = (Celsius)fahr;

4.2 隐式转换implicit

    一般情况下,隐式转换运算符应当从不引发异常且从不丢失信息,如果可能引发异常或丢失信息,则需要用显示转换符(并且用try catch捕获可能的异常),以便在用户不知晓的情况下安全地使用转换。

    示例:

class Digit
{
    public Digit(double d) { val = d; }
    public double val;
    // ...other members

    // User-defined conversion from Digit to double
    public static implicit operator double(Digit d)
    {
        return d.val;
    }
    //  User-defined conversion from double to Digit
    public static implicit operator Digit(double d)
    {
        return new Digit(d);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Digit dig = new Digit(7);
        //This call invokes the implicit "double" operator
        double num = dig;
        //This call invokes the implicit "Digit" operator
        Digit dig2 = 12;
        Console.WriteLine("num = {0} dig2 = {1}", num, dig2.val);
        Console.ReadLine();
    }
}

5. 扩展方法

    例如,要给StringBuilder扩展一个方法IndexOf(),可按照如下方式实现:

   public static class StringBuilderExtensions
    {
        public static int IndexOf(StringBuilder sb, char value)
        {
            for (int index = 0; index < sb.Length; index++)
            {
                if (sb[index] == value)
                    return index;
            }
            return -1;
        }

    }

然后调用:

            StringBuilder sb = new StringBuilder("Hello world.");
            int index = StringBuilderExtensions.IndexOf(sb.Replace('.', '!'), '!');

    但是这样的问题是:只能通过 类名.方法 来调用,使用不便,可做如下修改:

     public static class StringBuilderExtensions
     {
        public static int IndexOf(this StringBuilder sb, char value)

        {}

      }

    这样的话,可以直接使用StringBuilder的实例调用IndexOf方法,例如,sb.IndexOf('.')

C#扩展方法的意义:允许定义一个静态方法,并用实例方法的语法来调用。

5.1 扩展方法原则

    a. 只能扩展方法,不能扩展属性、事件、操作符等;

    b. 扩展方法(第一个参数前有this)必须在非泛型的静态类中声明。类名没有限制;

    c. 当多个静态类中定义了相同的方法时,就不能使用实例调用方法了,只能通过 类名.方法名调用;

5.2 为接口类型定义扩展方法

    例如:

    public static class IEnumerableExtensions
    {
        public static void ShowItems<T>(this IEnumerable<T> collection)
        {
            foreach (var item in collection)
            {
                Console.WriteLine(item);
            }
        }

    }

    则任何实现了IEnumerable<T>接口的类型都可以调用,例如 "Hello".ShowItems();

5.3 分部方法

    没看,partial 参考CLR 8.7

6. 参数

6.1 可选参数

    .NET4中新添加的功能,在设计方法的参数时,可为部分或全部参数分配默认值。然后调用方法时可以选择不提供部分实参,使用其默认值。

    例如:

        public static void Test(int x = 12, string y = "Frank")
        {
            Console.WriteLine(x.ToString() +":"+y);

        }

    可通过如下方式调用:

            Test();
            Test(13);
            Test(13,"Lenoard");

6.2 命名参数

    把参数附上参数名称(在方法中定义时的名称),这样在调用方法的时候不必按照原来的参数顺序填写参数,只要对应好参数的名称也可以调用方法,例如:

        public static string ShowInfo(string name, string title, string hobby)
        {
            return name + " " + title + " " + hobby;

        }

则可以这样调用:

    Console.WriteLine(ShowInfo(name:"Frank",hobby:"Fly in the sky",title:"Actor"));

原则:

a. 可以为方法、构造器、索引器

b. 有默认值的参数右侧必须都为可选参数;

c. 默认值必须是编译时能够确定的常量值;

d. 如果参数使用了ref out 关键字进行标识,就不能设置默认值了;

6.3 隐式类型的局部变量

    C#能根据初始化表达式的类型推断方法中的局部变量的类型,也就是var,使用var让编译器自动推断集合中的元素的类型。

6.4 以传引用的方式向方法传递参数

    CLR默认所有方法参数都传值,传递引用类型的对象时,对象引用(即指向对象的指针)被传给方法。注意引用(或指针)本身是传值的,意味着方法能修改对象,而调用者能看到这些修改。对于值类型,传给方法的是实例的一个副本,调用者中的实例不受影响。

    CLR允许以传引用而非传值的方式传递参数,C#通过ref out 关键字实现。

out:在调用方法前,参数不用被初始化,调用方法后,该变量值随方法中的逻辑而变化,且在方法返回前必须向这个值写入;

ref:在调用方法前,必须初始化参数的值,被调用的方法可以读取值/向值写入;

    从IL和CLR来看,out ref是同一码事:都导致传递指向实例的一个指针。但是对于编译器,会使用不同的标准检验代码是否正确。

    为值类型使用 out 和 ref,效果等同于以传值的方式传递引用类型。

6.5 传递可变数量的参数

    关键字 params;例如:

        private static int Add(params int[] values)
        {
            int sum = 0;
            if (values != null)
            {
                for (int x = 0; x < values.Length; x++)
                {
                    sum += values[x];
                }
            }
            return sum;

        }

注意:

    使用params是有性能上的损失的,毕竟数组对象必须在堆上分配,数组元素必须初始化,且数组的内存最终需要垃圾回收;为减少对性能的影响,可以考虑多定义几个没有params关键字的重载版本的方法(这些没有params的方法更长见,更常用),例如System.String的Concat方法:

    public static string Concat(string str0, string str1);

    public static string Concat(string str0, string str1, string str2);

    public static string Concat(string str0, string str1, string str2, string str3);

    public static string Concat(params string[] values);

6.6 参数和返回类型的设计规范

    声明方法的参数类型时,应尽量指定最弱的类型,宁愿要接口也不要基类(这样的话,当这个方法暴露给外部时,当你这边的代码改动时,例如返回值类型发生变化之类的,外部调用该函数的代码都要修改;如果返回一个接口,则无论方法代码如何改,只要接口不变,外部依赖的代码就不需要修改;当然,如果方法是在内部使用的话,则返回值无所谓)。例如,处理一组数据项,最好使用接口(例如IEnumerable<T>)声明参数,而不要用强数据类型(例如List<T>)或者更强的接口类型(例如ICollection<T>或IList<T>):

    //好:方法使用弱参数类型
    public void ManipulateItems<T>(IEnumerable<T> collection){...}
    //不好:方法使用强参数类型
    public void ManipulateItems<T>(IEnumerable<T> collection){...}    

    第一个方法更好,它更灵活,使用广泛;

    当然,如果方法需要的是列表(而不是任何可枚举的对象),就应将参数类型声明为IList<T>,但仍避免将参数声明为List<T>。

    相反,一般最好是将方法的返回类型声明为最强的类型(防止受限于特定类型)。


7. 属性

a. 无参属性:平常所说的属性

b. 有参属性:在C#中被称为索引器

7.1 特殊的对象初始化语法

    Employee e = new Employee(){ Name ="Frank", Age = 23 };

等价于:

    Employee e = new Employee()

    e.Name = "Frank";

    e.Age = 23;

匿名类型:var value = new { property1=expression1, ... , propertyN=expressionN};

集合:实现了IEnumberable或IEnumberable<T>接口的对象;

7.2 有参属性

    get访问器方法接受一个或多个参数,set访问器方法接受两个或多个参数,C#中称为索引器。

    索引器blog:http://www.cnblogs.com/ArmyShen/archive/2012/08/27/2659405.html

8.泛型 泛型约束

    泛型:作用是“代码重用”,用于“算法重用”。CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型,还可以创建泛型接口和泛型委托。

    原则:泛型参数变量为T,或者以T开头(如TKey TValue),类似于接口I的原则;

8.1 泛型

    泛型最常见的应用是集合类。

8.2 泛型约束

    参考:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters

8.2.1 泛型约束种类

    在定义泛型类时,可以对客户端代码实例化类时所使用的类型参数添加一些约束;如果客户端代码尝试使用约束所不允许的类型来实例化类,则会产生编译错误。通过使用where关键字指定约束,下面为六种类型的约束:

a. where T: struct:类型参数必须是值类型。可以指定除nullable外的任何值类型。

b. where T: class:类型参数必须是引用类型;这同样适用于所有类、接口、委托或数组类型;

c. where T: new():类型参数必须具有public无参的构造函数;当与其他约束一起使用时,new()必须放到最后;

d. where T: <基类名>:类型参数必须是约束中的基类,或基类的派生类型;

e. where T: <接口名称>:类型参数必须是指定接口,或实现指定接口的类型,可指定多个接口约束,约束接口也可以是泛型;

f. where T: <接口名称>:msdn没理解;

8.2.2 使用泛型约束原因

    例如在一个泛型类中,要检查泛型列表中的某个项是否有效,或者将它与其他项比较,那么编译器必须保证client代码指定的参数支持它需要调用运算符或者方法,因此就需要添加泛型约束。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值