unity游戏开发-7-unity 基础:继承、接口、属性、索引器、事件、访问控制

前言

上一篇文章介绍了 c# 中基础的:数据类型、条件、循环、函数、委托、泛型 相关的内容。对于自定义的数据类型:类、结构,其非常重要的特性—— 派生 没有详细说明。
本篇文章将介绍 c# 中和 派生 相关的内容,包括:继承|派生、接口、属性、访问控制。

1. 继承

  • 父类 & 子类

    • 父类也称基类,子类也称派生类
    • 父类派生出子类,或者说 子类继承自父类
    • 比如一个简单的父类:动物,子类1:猫,子类2:狗
  • 继承的用途

    • 描述类的逻辑关系:在 面向对象 的编程语言中,一般都会提供 类、继承 相关的语法,用以描述 某一系列 抽象集合 的 is-a 关系。
    • 代码复用:在派生类中可以仅重写于父类有差异的函数,达到对无差异的函数的复用
    • 多态:基类引用子类的实例时,调用该引用的函数,实际调用的是实际子类对象的函数,也就是用基类类型可以实现对不同子类函数的访问。
  • 代码样例(类继承)

    • 基类

      public class Animal
      {
          public string Name { get; set; }
      
          public Animal(string name)
          {
              Name = name;
          }
      
          public virtual void MakeSound()
          {
              Console.WriteLine("This animal makes a sound.");
          }
      }
      
    • 派生类

      public class Dog : Animal
      {
          public Dog(string name) : base(name)
          {
          }
      
          public override void MakeSound()
          {
              Console.WriteLine("Woof!");
          }
      }
      
    • 调用(多态)

      public class Program
      {
          public static void Main()
          {
              Animal myAnimal = new Animal("Generic Animal");
              myAnimal.MakeSound(); // 输出:This animal makes a sound.
      
              Dog myDog = new Dog("Buddy");
              myDog.MakeSound(); // 输出:Woof!
      
              // 多态:通过基类引用调用派生类的方法
              Animal myAnimalDog = new Dog("Max");
              myAnimalDog.MakeSound(); // 输出:Woof!
          }
      }
      
  • c# 中的 “基类”

    • 除了 class,在 c# 中还提供了 接口(Interface)用于定义接口,可以被继承。(在第2节详细介绍)

2. 接口

2.1 用途 & 优势

  • 定义契约

    • 接口定义了一组方法、属性、索引器或事件的签名,但不提供具体实现。实现接口的类或结构体必须提供这些成员的具体实现。
    • 这种方式定义了类或结构体必须遵守的契约,确保它们具有一致的行为。
  • 实现多态

    • 多个类可以实现同一个接口,从而实现多态。通过接口引用,可以调用接口中定义的方法,而不关心具体实现。
    • 这使得代码更加灵活,可以编写通用的代码来处理不同类型的对象。
  • 解耦
    接口可以减少类之间的耦合度,使得代码更加灵活和可维护。实现接口的类只需要关心接口定义的成员,而不需要知道其他类的具体实现。

  • 代码复用
    接口允许不同类共享相同的方法签名,从而提高代码的复用性。

  • 扩展性
    接口可以很容易地扩展。如果需要添加新的方法或属性,可以在接口中添加新的签名,而不需要修改现有的实现。

2.2 可以定义的内容

  • 方法(Methods):
    接口可以定义方法的签名,不提供具体实现(c# 8.0 后可以定义默认实现)。
    实现接口的类或结构体必须提供这些方法的具体实现。

  • 属性(Properties):
    接口可以定义属性的签名,但不提供具体实现。
    实现接口的类或结构体必须提供这些属性的具体实现。

  • 索引器(Indexers):
    接口可以定义索引器的签名,但不提供具体实现。
    实现接口的类或结构体必须提供这些索引器的具体实现。

  • 事件(Events):
    接口可以定义事件的签名,但不提供具体实现。
    实现接口的类或结构体必须提供这些事件的具体实现。

2.3 特点

  • 接口本身不能实例化,只能通过实现接口的类或结构体来实例化。
  • 接口中的成员默认是 public,不能有访问修饰符
  • 接口可以继承接口,支持多继承。
  • 一个类可以实现多个接口,从而实现多继承的效果。
  • C# 8.0 开始,接口中的函数可以提供默认实现,实现接口的类可以选择性地覆盖这些方法。

2.2 定义 & 继承

  • 关键字

    • interface
    • 类似于 类的关键字 class
  • 代码样例

    • 接口定义

      public interface IAnimal
      {
          void MakeSound();
      }
      
      public interface INameable
      {
          string Name { get; set; }
      }
      
      public interface IPet : IAnimal, INameable
      {
          void Play();
      }
      

      注:上面这个例子的特点有:
      ① 接口多继承:IPet 接口继承了多个接口
      ② IAnimal 接口中不实现具体的方法
      ③ INameable 接口中定义了属性

    • 继承:接口实现

      public class Dog : IPet
      {
          public string Name { get; set; }
      
          public void MakeSound()
          {
              Console.WriteLine("Woof!");
          }
      
          public void Play()
          {
              Console.WriteLine($"{Name} is playing.");
          }
      }
      
      public class Cat : IPet
      {
          public string Name { get; set; }
      
          public void MakeSound()
          {
              Console.WriteLine("Meow!");
          }
      
          public void Play()
          {
              Console.WriteLine($"{Name} is playing.");
          }
      }
      

2.3 使用 & 多态

  • 代码样例
public class Program
{
    public static void Main()
    {
        IPet dog = new Dog { Name = "Buddy" };
        IPet cat = new Cat { Name = "Whiskers" };

        dog.MakeSound(); // 输出:Woof!
        dog.Play();      // 输出:Buddy is playing.

        cat.MakeSound(); // 输出:Meow!
        cat.Play();      // 输出:Whiskers is playing.
    }
}

3. 属性

3.1 用途

在 C# 中,属性(Property) 是一种特殊的成员,它允许你访问类或结构体中的私有字段,同时提供了一种更安全和灵活的方式来控制对字段的访问。属性通常用于封装字段,隐藏字段的实现细节,并提供额外的逻辑(如验证输入)。

3.2 语法

3.2.1 简写

  • 代码样例
public class MyClass
{
    // 定义一个自动实现的属性
    public int Property { get; set; }
}

public class Program
{
    public static void Main()
    {
        MyClass obj = new MyClass();

        // 设置属性的值
        obj.Property = 10;

        // 获取属性的值
        Console.WriteLine(obj.Property); // 输出:10
    }
}
  • 注:虽然不需要显式定义私有字段,但 编译器会自动生成 一个私有字段来存储属性的值。

3.2.2 完整写法:定义私有成员、实现 get|set

  • 代码示例
public class MyClass
{
    private int _property; // 私有字段

    // 定义一个属性
    public int Property
    {
        get { return _property; } // 返回私有字段的值
        set
        {
            if (value < 0)
            {
                throw new ArgumentException("Value cannot be negative.");
            }
            _property = value; // 设置私有字段的值
        }
    }
}

public class Program
{
    public static void Main()
    {
        MyClass obj = new MyClass();

        // 设置属性的值
        obj.Property = 10;

        // 获取属性的值
        Console.WriteLine(obj.Property); // 输出:10

        // 尝试设置一个负值
        try
        {
            obj.Property = -5;
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine(ex.Message); // 输出:Value cannot be negative.
        }
    }
}

3.2.3 只读 & 只写 属性

  • 只读:仅定义 get

    • 代码样例

      public class MyClass
      {
          private int _field; // 私有字段
      
          // 只读属性
          public int ReadOnlyProperty
          {
              get { return _field; }
          }
      
          // 公共方法用于设置只读属性的值
          public void SetProperty(int value)
          {
              _field = value; // 通过私有字段设置值
          }
      }
      
  • 只写:仅定义 set

    • 代码样例
    public class MyClass
    {
        private int _field; // 私有字段
    
        // 只写属性
        public int WriteOnlyProperty
        {
            set { _field = value; }
        }
    
        // 公共方法用于获取只写属性的值
        public int GetProperty()
        {
            return _field; // 通过私有字段获取值
        }
    }
    

4. 索引器

4.1 用途

  • 在 C# 中,索引器(Indexer) 是一种特殊的成员,它允许对象以类似数组的方式被索引。索引器可以提供基于整数、字符串或其他类型的索引访问,使得对象的使用更加灵活和直观。
  • 索引器通常用于以下场景:
    • 提供基于索引的访问:允许对象像数组或字典一样被访问。
    • 封装内部数据结构:隐藏内部数据的实现细节,只暴露索引访问接口。
    • 提供自定义的索引逻辑:可以基于不同的键类型(如字符串、整数等)提供自定义的索引访问。

4.2 语法

  • 整型索引

    • 代码样例

      • 定义
      public class MyCollection
      {
          private string[] _items = new string[10];
      
          public string this[int index]
          {
              get
              {
                  if (index < 0 || index >= _items.Length)
                  {
                      throw new IndexOutOfRangeException("Index out of range.");
                  }
                  return _items[index];
              }
              set
              {
                  if (index < 0 || index >= _items.Length)
                  {
                      throw new IndexOutOfRangeException("Index out of range.");
                  }
                  _items[index] = value;
              }
          }
      }
      
      • 使用
      public class Program
      {
          public static void Main()
          {
              MyCollection collection = new MyCollection();
      
              // 设置值
              collection[0] = "Item 1";
              collection[1] = "Item 2";
      
              // 获取值
              Console.WriteLine(collection[0]); // 输出:Item 1
              Console.WriteLine(collection[1]); // 输出:Item 2
          }
      }
      
  • 字符串索引

    • 代码样例
      • 定义
      public class MyDictionary
      {
          private Dictionary<string, string> _data = new Dictionary<string, string>();
      
          public string this[string key]
          {
              get
              {
                  if (_data.ContainsKey(key))
                  {
                      return _data[key];
                  }
                  throw new KeyNotFoundException("Key not found.");
              }
              set
              {
                  _data[key] = value;
              }
          }
      }
      
      • 使用
      public class Program
      {
          public static void Main()
          {
              MyDictionary dictionary = new MyDictionary();
      
              // 设置值
              dictionary["key1"] = "Value 1";
              dictionary["key2"] = "Value 2";
      
              // 获取值
              Console.WriteLine(dictionary["key1"]); // 输出:Value 1
              Console.WriteLine(dictionary["key2"]); // 输出:Value 2
          }
      }
      

5. 事件

5.1 用途

  • 在 C# 中,事件(Event) 是一种特殊的成员,用于实现发布-订阅模式。事件允许类或对象在特定情况下通知其他对象,从而实现对象之间的解耦和交互。事件通常用于处理用户界面事件(如按钮点击、窗口关闭等),但也可以用于任何需要通知的场景。

  • 事件的基本概念

    • 发布者(Publisher):
      定义事件的类或对象,负责触发事件。
      通常包含一个或多个事件成员,以及触发事件的方法。

    • 订阅者(Subscriber):
      注册事件的类或对象,负责处理事件。
      通过事件处理器(Event Handler)接收事件通知。

    • 事件处理器(Event Handler):
      一个方法,用于处理事件。
      事件处理器的签名必须与事件的委托类型匹配。

5.2 语法

5.2.1 定义

  • 事件(发布者)
    • 事件的定义通常包括以下步骤:
      定义一个委托(Delegate),用于指定事件处理器的签名。
      定义一个事件成员,使用 event 关键字。
      提供一个方法来触发事件。

    • 代码示例

using System;

public class EventPublisher
{
    // 定义委托
    public delegate void MyEventHandler(object sender, EventArgs e);

    // 定义事件
    public event MyEventHandler MyEvent;

    // 触发事件的方法
    public void DoSomething()
    {
        Console.WriteLine("Doing something...");

        // 触发事件
        OnMyEvent();
    }

    // 触发事件的辅助方法
    protected virtual void OnMyEvent()
    {
        MyEvent?.Invoke(this, EventArgs.Empty);
    }
}
  • 订阅者
    • 订阅者需要注册事件,并提供事件处理器来处理事件。
    • 代码示例
using System;

public class EventSubscriber
{
    public void Subscribe(EventPublisher publisher)
    {
        // 注册事件
        publisher.MyEvent += HandleEvent;
    }

    public void Unsubscribe(EventPublisher publisher)
    {
        // 取消注册事件
        publisher.MyEvent -= HandleEvent;
    }

    // 事件处理器
    public void HandleEvent(object sender, EventArgs e)
    {
        Console.WriteLine("Event handled by EventSubscriber.");
    }
}

5.2.2 使用

  • 代码示例
using System;

public class Program
{
    public static void Main()
    {
        EventPublisher publisher = new EventPublisher();
        EventSubscriber subscriber = new EventSubscriber();

        // 注册事件
        subscriber.Subscribe(publisher);

        // 触发事件
        publisher.DoSomething(); // 输出:Doing something... Event handled by EventSubscriber.

        // 取消注册事件
        subscriber.Unsubscribe(publisher);

        // 再次触发事件(不会输出)
        publisher.DoSomething(); // 输出:Doing something...
    }
}

6. 访问控制

在 C# 中,访问控制用于限制代码的可见性和访问权限。C# 提供了五种访问修饰符,分别是 publicprivateprotectedinternalprotected internal。每种访问修饰符都有其特定的用途和作用范围。

6.1 访问修饰符及其作用范围

  • public

    • 作用范围:任何地方都可以访问。
    • 用途:用于公开类、方法、属性等,使其可以被外部代码访问。
  • private

    • 作用范围:仅在定义它的类或结构体内部可以访问。
    • 用途:用于隐藏类或结构体的内部实现细节,防止外部代码直接访问。
  • protected

    • 作用范围:仅在定义它的类或结构体及其派生类中可以访问。
    • 用途:用于允许派生类访问基类的成员,但不允许外部代码访问。
  • internal

    • 作用范围:仅在同一个程序集(Assembly)内可以访问。
    • 用途:用于限制类或成员的访问范围,使其仅在当前项目内可见。
  • protected internal

    • 作用范围:在同一个程序集内或派生类中可以访问。
    • 用途:结合了 protectedinternal 的特性,允许在当前程序集内或派生类中访问。

6.2 访问控制的具体应用

  • 类(Class)
    类的访问修饰符决定了类的可见性。
public class PublicClass
{
    // 公共类,任何地方都可以访问
}

private class PrivateClass
{
    // 私有类,仅在定义它的类或结构体内部可以访问
}

internal class InternalClass
{
    // 内部类,仅在同一个程序集内可以访问
}
  • 成员(Members)
    成员(如字段、方法、属性等)的访问修饰符决定了它们的可见性。
public class MyClass
{
    public int PublicField; // 公共字段,任何地方都可以访问
    private int PrivateField; // 私有字段,仅在类内部可以访问
    protected int ProtectedField; // 受保护字段,仅在类及其派生类中可以访问
    internal int InternalField; // 内部字段,仅在同一个程序集内可以访问
    protected internal int ProtectedInternalField; // 受保护内部字段,仅在同一个程序集内或派生类中可以访问
}
  • 方法(Methods)
    方法的访问修饰符决定了它们的可见性。
public class MyClass
{
    public void PublicMethod()
    {
        // 公共方法,任何地方都可以访问
    }

    private void PrivateMethod()
    {
        // 私有方法,仅在类内部可以访问
    }

    protected void ProtectedMethod()
    {
        // 受保护方法,仅在类及其派生类中可以访问
    }

    internal void InternalMethod()
    {
        // 内部方法,仅在同一个程序集内可以访问
    }

    protected internal void ProtectedInternalMethod()
    {
        // 受保护内部方法,仅在同一个程序集内或派生类中可以访问
    }
}
  • 属性(Properties)
    属性的访问修饰符决定了它们的可见性。属性的 getset 访问器也可以有不同的访问修饰符。
public class MyClass
{
    public int PublicProperty { get; set; } // 公共属性,任何地方都可以访问
    private int PrivateProperty { get; set; } // 私有属性,仅在类内部可以访问
    protected int ProtectedProperty { get; set; } // 受保护属性,仅在类及其派生类中可以访问
    internal int InternalProperty { get; set; } // 内部属性,仅在同一个程序集内可以访问
    protected internal int ProtectedInternalProperty { get; set; } // 受保护内部属性,仅在同一个程序集内或派生类中可以访问

    // 属性的访问器可以有不同的访问修饰符
    public int ReadOnlyProperty { get; private set; } // 只读属性,外部只能读取,不能设置
    public int WriteOnlyProperty { private get; set; } // 只写属性,外部只能设置,不能读取
}
  • 接口(Interfaces)
    接口的成员(方法、属性等)默认是 public,不能显式指定访问修饰符。接口本身可以使用 publicinternalprotected internal 修饰符。
public interface IMyInterface
{
    void Method(); // 默认是 public
    int Property { get; set; } // 默认是 public
}

6.3 访问控制的具体示例

  • 类和成员
public class MyClass
{
    public int PublicField; // 公共字段
    private int PrivateField; // 私有字段
    protected int ProtectedField; // 受保护字段
    internal int InternalField; // 内部字段
    protected internal int ProtectedInternalField; // 受保护内部字段

    public void PublicMethod() // 公共方法
    {
        Console.WriteLine("Public method");
    }

    private void PrivateMethod() // 私有方法
    {
        Console.WriteLine("Private method");
    }

    protected void ProtectedMethod() // 受保护方法
    {
        Console.WriteLine("Protected method");
    }

    internal void InternalMethod() // 内部方法
    {
        Console.WriteLine("Internal method");
    }

    protected internal void ProtectedInternalMethod() // 受保护内部方法
    {
        Console.WriteLine("Protected internal method");
    }

    public int PublicProperty { get; set; } // 公共属性
    private int PrivateProperty { get; set; } // 私有属性
    protected int ProtectedProperty { get; set; } // 受保护属性
    internal int InternalProperty { get; set; } // 内部属性
    protected internal int ProtectedInternalProperty { get; set; } // 受保护内部属性
}
  • 派生类
public class DerivedClass : MyClass
{
    public void Test()
    {
        PublicMethod(); // 可以访问
        // PrivateMethod(); // 错误:私有方法不可访问
        ProtectedMethod(); // 可以访问
        // InternalMethod(); // 错误:内部方法不可访问(除非在同一程序集中)
        ProtectedInternalMethod(); // 可以访问
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值