浅度复制(Shallow Copy):创建一个新对象,该对象与原始对象具有相同的值类型字段。引用类型字段的值也将被复制,但是它们仍将引用原始对象中的同一对象。简而言之,浅度复制只复制引用而不复制引用的对象。
深度复制(Deep Copy):创建一个新对象,该对象与原始对象具有相同的值类型字段。引用类型字段的值也将被复制,但是它们指向的对象也将被复制,而不是只复制引用。换句话说,深度复制不仅复制引用,还会复制引用的对象。
下面是一个示例,演示如何在C#中执行浅度和深度复制:
using System;
class Person
{
public string Name;
public int Age;
public Address Address;
public Person(string name, int age, Address address)
{
Name = name;
Age = age;
Address = address;
}
}
class Address
{
public string City;
public string Country;
public Address(string city, string country)
{
City = city;
Country = country;
}
}
class Program
{
static void Main(string[] args)
{
// Create a person object
Address address = new Address("New York", "USA");
Person person1 = new Person("John", 30, address);
// Shallow copy
Person person2 = (Person)person1.MemberwiseClone();
// Deep copy
Person person3 = new Person(person1.Name, person1.Age, new Address(person1.Address.City, person1.Address.Country));
// Change the original object
person1.Name = "Mike";
person1.Age = 35;
person1.Address.City = "Chicago";
// Output the results
Console.WriteLine("Shallow copy:");
Console.WriteLine("Person 1: " + person1.Name + " " + person1.Age + " " + person1.Address.City + " " + person1.Address.Country);
Console.WriteLine("Person 2: " + person2.Name + " " + person2.Age + " " + person2.Address.City + " " + person2.Address.Country);
Console.WriteLine("Deep copy:");
Console.WriteLine("Person 1: " + person1.Name + " " + person1.Age + " " + person1.Address.City + " " + person1.Address.Country);
Console.WriteLine("Person 3: " + person3.Name + " " + person3.Age + " " + person3.Address.City + " " + person3.Address.Country);
}
}
输出结果为:
Shallow copy:Person 1: Mike 35 Chicago USAPerson 2: John 30 Chicago USA
Deep copy:Person 1: Mike 35 Chicago USAPerson 3: John 30 New York USA
可以看到,浅度复制只复制了引用类型的引用,而深度复制复制了整个对象,包括它所引用的对象。因此,在更改原始对象后,浅度复制的对象也会受到影响,而浅复制和深复制是指在进行对象复制时,复制出的新对象与原对象之间的关系。浅复制是复制出一个新对象,但该新对象中的引用类型成员变量与原对象中的引用类型成员变量指向同一个地址;而深复制则是复制出一个新对象,该新对象与原对象互不影响。
浅复制
在C#中,通过实现ICloneable接口可以实现浅复制。ICloneable接口中定义了一个名为Clone的方法,返回一个与当前对象相同的新对象,如下所示:
public interface ICloneable
{
object Clone();
}
在实现ICloneable接口的类中,需要重写Clone方法,并使用MemberwiseClone方法进行浅复制。MemberwiseClone方法可以将当前对象的值类型成员变量复制到新对象中,并将引用类型成员变量的引用复制到新对象中。
例如:
class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
public object Clone()
{
return MemberwiseClone();
}
}
class Address
{
public string Street { get; set; }
public string City { get; set; }
}
// 使用
Person p1 = new Person { Name = "John", Age = 30, Address = new Address { Street = "Main St.", City = "New York" } };
Person p2 = (Person)p1.Clone();
p2.Name = "Tom";
p2.Address.City = "Los Angeles";
Console.WriteLine(p1.Name); // John
Console.WriteLine(p1.Address.City); // Los Angeles
在上面的示例中,通过p1.Clone()实现了对p1对象的浅复制。当修改p2对象的Name属性时,p1对象的Name属性不受影响,因为Name是值类型成员变量。但是,当修改p2对象的Address.City属性时,p1对象的Address.City属性也会被修改,因为Address是引用类型成员变量,它们共享同一个对象。
深复制
要实现深复制,需要遍历整个对象图,并为每个引用类型成员变量创建新对象。在C#中,可以通过序列化和反序列化来实现深复制。具体步骤如下:
在需要进行深复制的类中实现ISerializable接口,并重写GetObjectData方法。
public:表示该成员是公共的,可以在任何地方访问。
private:表示该成员是私有的,只能在当前类中访问。
protected:表示该成员是受保护的,只能在当前类和其子类中访问。
internal:表示该成员是内部的,只能在同一程序集中访问。
protected internal:表示该成员是既受保护又是内部的,可以在同一程序集中以及其子类中访问。
public class Person
{
// 字段
private string name;
// 属性
public string Name
{
get { return name; }
set { name = value; }
}
// 构造函数
public Person(string name)
{
this.name = name;
}
// 方法
public void SayHello()
{
Console.WriteLine("Hello, my name is " + name);
}
}
在这个例子中,我们定义了一个Person类,包括了字段、属性、构造函数和方法。其中,字段使用了private关键字修饰,属性使用了public关键字修饰,构造函数和方法也使用了public关键字修饰。这样就定义了一个可以在其他地方访问的类成员。
public string name; // 公共访问修饰符的字符串类型字段
private int age; // 私有访问修饰符的整数类型字段
protected double height; // 受保护访问修饰符的双精度浮点型字段
internal bool isMale; // 内部访问修饰符的布尔类型字段
上述代码中,四个字段分别被定义为 name、age、height 和 isMale。访问修饰符决定了这些字段可以被哪些代码块访问。除了访问修饰符和数据类型之外,还可以在字段定义时指定默认值,如下所示:
public string name = "Tom"; // 定义一个初始值为 "Tom" 的字符串类型字段
private int age = 18; // 定义一个初始值为 18 的整数类型字段
protected double height = 1.75; // 定义一个初始值为 1.75 的双精度浮点型字段
internal bool isMale = true; // 定义一个初始值为 true 的布尔类型字段
通过为字段指定默认值,可以在创建对象时,自动将字段的值初始化为指定的值。
[可见性修饰符] [静态修饰符] 返回类型 方法名([参数列表]) [异常列表] {
// 方法体 }
其中,可见性修饰符可以是 public、private、protected 和 internal,表示该方法对外的可见性。静态修饰符可以是 static,表示该方法为静态方法。
返回类型可以是任何类型,包括 void 类型,表示该方法不返回任何值。
方法名可以是任何符合命名规范的名称,可以包括字母、数字、下划线和美元符号等。
参数列表包括方法的输入参数,可以有多个参数,每个参数由类型和名称组成。
异常列表可以包含该方法可能抛出的异常类型,用于在编译时进行检查。
public static int Add(int a, int b) {
return a + b;
}
该方法为 public 静态方法,返回类型为 int,方法名为 Add,参数列表包含两个 int 类型的参数 a 和 b,方法体为计算 a 和 b 的和并返回结果。
在C#中,可以通过以下方式定义属性:
public class MyClass
{
private int _myField;
public int MyProperty
{
get { return _myField; }
set { _myField = value; }
}
}
在这个示例中,我们定义了一个名为 MyClass 的类,该类具有一个名为 _myField 的私有字段和一个名为 MyProperty 的公共属性。 MyProperty 属性使用 get 和 set 访问器方法,它们分别用于获取和设置私有字段的值。
var myObject = new MyClass();
myObject.MyProperty = 42;
int value = myObject.MyProperty;
在此示例中,我们创建了一个 MyClass 实例,并将其 MyProperty 属性设置为整数值 42。 然后,我们将属性值分配给变量 value。
C#重构成员
C#重构成员指的是对现有的类成员进行修改而不影响现有的程序功能。常见的重构成员包括重命名、提取方法、封装字段等。
通过对类成员进行重构,可以提高代码的可读性、可维护性和可扩展性,使代码更加清晰简洁。
public class Person
{
private string name;
private int age;
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
public void Print()
{
Console.WriteLine($"Name: {name}, Age: {age}");
}
public string GetName()
{
return name;
}
public int GetAge()
{
return age;
}
}
我们发现在 GetAge 和 GetName 方法中,只是返回了对应的字段的值,这样的代码不够简洁,也不够易读。我们可以将这些字段改为属性的形式,使代码更加简洁易读:
public class Person
{
public string Name { get; }
public int Age { get; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public void Print()
{
Console.WriteLine($"Name: {Name}, Age: {Age}");
}
}
这样,我们就使用了属性来代替字段,并且将 GetName 和 GetAge 方法删除了,使代码更加简洁易读。这就是重构成员的一个例子。
第三点重构成员指的是对现有的代码中的成员进行修改和优化,包括重命名、更改成员的访问修饰符、更改成员的类型、添加/移除成员、修改方法签名等操作,以提高代码的可读性、可维护性和性能等方面的优化。
举例来说,可以通过重命名成员的名称,使代码更符合命名规范和语义化,从而提高代码的可读性和可维护性。例如,将一个名为“xyz”的私有字段改名为“_xyz”更符合命名规范。
另外,如果一个成员的访问修饰符不当,也会影响代码的可维护性。例如,如果一个字段应该只在类内部使用,但却被设置为公有的,那么这个字段就会被其他代码修改,从而导致代码出现错误。在这种情况下,可以将字段的访问修饰符从公有的改为私有的,以限制对它的访问。
还可以通过修改成员的类型来提高代码的性能和可维护性。例如,如果一个字符串被频繁修改,可以将其改为StringBuilder类型,以避免频繁的字符串拼接操作,提高代码的性能。
除此之外,还可以添加/移除成员来优化代码。例如,如果发现一个方法的代码行数过多,可以将其中一部分代码封装到另一个方法中,以减少代码的复杂性。或者如果发现一个成员没有被使用,可以将其移除,以避免代码中出现冗余的成员。
总之,重构成员是提高代码质量的重要手段之一,可以使代码更易于阅读、理解和维护,同时还可以提高代码的性能和可扩展性。
C#中的自动属性是一种简化了属性定义的方式,使属性定义更加简洁。在定义一个自动属性时,编译器会自动生成一个私有的后备字段,并在编译时自动为该字段生成get和set方法。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
在这个示例中,我们定义了一个名为Person的类,并在其中定义了两个自动属性:Name和Age。在这里,我们没有手动定义任何后备字段或get和set方法,而是让编译器自动生成这些代码。
我们可以像访问普通属性一样访问这些自动属性:
Person p = new Person();
p.Name = "John";p.Age = 30;
Console.WriteLine("Name: {0}, Age: {1}", p.Name, p.Age);
//这将输出以下内容:
//yamlCopy code
//Name: John, Age: 30
需要注意的是,由于自动属性会自动生成后备字段,因此它们不能与手动定义的属性或字段重名。此外,自动属性不能具有不同的可访问性级别,即它们的get和set方法必须具有相同的可访问性级别。
在C#中,可以通过在派生类中定义与基类中相同的方法名称来隐藏基类方法。这被称为方法隐藏。通常,隐藏基类方法的原因是为了提供一个新的实现,或者是为了屏蔽基类方法的实现细节。
方法隐藏可以使用new关键字来实现。
class BaseClass
{
public void Print()
{
Console.WriteLine("BaseClass.Print");
}
}
class DerivedClass : BaseClass
{
public new void Print()
{
Console.WriteLine("DerivedClass.Print");
}
}
在这个例子中,DerivedClass重写了BaseClass中的Print()方法,使其输出DerivedClass.Print。注意,new关键字是可选的,但是如果不使用它,编译器会发出一个警告。
class DerivedClass : BaseClass
{
public new void Print()
{
Console.WriteLine("DerivedClass.Print");
base.Print(); // 调用基类方法
}
}
//在这个例子中,DerivedClass.Print()方法输出DerivedClass.Print,
//然后调用基类的Print()方法,输出BaseClass.Print。
在 C# 中,调用重写或隐藏的基类方法通常使用 base 关键字。如果子类想要调用基类中被重写或隐藏的方法,可以使用 base 关键字指定调用基类中的方法。
例如,如果子类重写了基类的一个方法,但仍希望在该方法中调用基类的实现,可以使用 base 关键字。以下是一个示例:
class MyBaseClass
{
public virtual void MyMethod()
{
Console.WriteLine("MyBaseClass.MyMethod");
}
}
class MyDerivedClass : MyBaseClass
{
public override void MyMethod()
{
Console.WriteLine("MyDerivedClass.MyMethod");
base.MyMethod();
}
}
class Program
{
static void Main(string[] args)
{
MyDerivedClass obj = new MyDerivedClass();
obj.MyMethod();
}
}
//在上面的示例中,MyDerivedClass 重写了 MyBaseClass 的 MyMethod 方法,
//并在其中使用了 base.MyMethod() 来调用 MyBaseClass 的实现。这将导致控制台输出以下内容:
//MyDerivedClass.MyMethod
//MyBaseClass.MyMethod
//需要注意的是,base 关键字只能用于调用基类中的成员。如果需要调用同一类中的另一个方法,可以直接使用方法名调用即可。
C#中的嵌套类型定义是指在一个类的内部定义另一个类。嵌套类型可以是任何类型,包括类、结构体、枚举和接口。嵌套类型可以具有与外部类完全不同的访问修饰符和继承关系。
public class OuterClass
{
private string outerName = "Outer";
public class NestedClass
{
private string nestedName = "Nested";
public void DisplayNames(string outerName)
{
Console.WriteLine("Outer name: {0}", outerName);
Console.WriteLine("Nested name: {0}", nestedName);
}
}
}
//在这个示例中,OuterClass包含了一个私有字段outerName和一个公共的嵌套类NestedClass。
//NestedClass包含了一个私有字段nestedName和一个公共方法DisplayNames,
//该方法可以显示传递给它的外部类字段的值和嵌套类字段的值。
//要使用嵌套类型,需要使用外部类的实例创建它的实例,如下所示:
OuterClass outer = new OuterClass();
OuterClass.NestedClass nested = new OuterClass.NestedClass();
nested.DisplayNames(outer.outerName);
在这个示例中,首先创建了外部类的实例outer,然后使用这个实例创建了嵌套类的实例nested。最后,调用nested的DisplayNames方法,将外部类的outerName字段的值传递给它。
C#中的部分类定义允许将一个类分成多个源文件来定义。这通常用于将一个类的实现分成多个逻辑部分,以便更好地组织和管理代码。在C#中,部分类定义可以通过在类名称前面加上关键字 partial 来实现
public partial class MyClass
{
public void Method1() { }
}
public partial class MyClass
{
public void Method2() { }
}
在上面的代码中,MyClass 类被定义为两个部分,分别定义了 Method1 和 Method2 两个方法。这两个部分可以分别位于不同的源文件中,只要它们共同组成了同一个类。当编译器编译这些源文件时,它们会被合并成一个类定义。
需要注意的是,部分类定义仅适用于类、结构和接口类型,而不适用于枚举类型或委托类型。另外,如果一个类被定义为部分类,那么所有的部分必须使用相同的可访问性修饰符,否则编译器会报错。