수색…
통사론
- DerivedClass 클래스 : BaseClass
- DerivedClass 클래스 : BaseClass, IExampleInterface
- 클래스 DerivedClass : BaseClass, IExampleInterface, IAnotherInterface
비고
클래스는 하나의 클래스에서 직접 상속받을 수 있지만 (대신 또는 동시에) 하나 이상의 인터페이스를 구현할 수 있습니다.
구조체는 인터페이스를 구현할 수 있지만 명시 적으로 모든 유형을 상속 할 수는 없습니다. System.ValueType
암시 적으로 상속하며 System.Object
에서 직접 상속합니다.
기본 클래스에서 상속
코드 복제를 피하려면 일반 클래스의 공통 메소드 및 속성을 기본 클래스로 정의하십시오.
public class Animal
{
public string Name { get; set; }
// Methods and attributes common to all animals
public void Eat(Object dinner)
{
// ...
}
public void Stare()
{
// ...
}
public void Roll()
{
// ...
}
}
일반적으로 Animal
을 나타내는 클래스가 있으므로 특정 동물의 특성을 나타내는 클래스를 정의 할 수 있습니다.
public class Cat : Animal
{
public Cat()
{
Name = "Cat";
}
// Methods for scratching furniture and ignoring owner
public void Scratch(Object furniture)
{
// ...
}
}
Cat 클래스는 정의에 명시 적으로 설명 된 메소드뿐만 아니라 일반 Animal
기본 클래스에 정의 된 모든 메소드에 액세스 할 수 있습니다. 어떤 동물 (그것이 고양이이든 아니든)은 먹거나, 찌르거나, 굴릴 수 있습니다. 그러나 동물도 고양이가 아니면 동물은 긁을 수 없습니다. 그런 다음 다른 동물을 묘사하는 다른 클래스를 정의 할 수 있습니다. (Gopher와 같은 방법으로 꽃밭과 Sloth를 파괴하는 방법은 전혀 없습니다.)
클래스에서 상속 받고 인터페이스를 구현
public class Animal
{
public string Name { get; set; }
}
public interface INoiseMaker
{
string MakeNoise();
}
//Note that in C#, the base class name must come before the interface names
public class Cat : Animal, INoiseMaker
{
public Cat()
{
Name = "Cat";
}
public string MakeNoise()
{
return "Nyan";
}
}
클래스에서 상속 받고 다중 인터페이스 구현하기
public class LivingBeing
{
string Name { get; set; }
}
public interface IAnimal
{
bool HasHair { get; set; }
}
public interface INoiseMaker
{
string MakeNoise();
}
//Note that in C#, the base class name must come before the interface names
public class Cat : LivingBeing, IAnimal, INoiseMaker
{
public Cat()
{
Name = "Cat";
HasHair = true;
}
public bool HasHair { get; set; }
public string Name { get; set; }
public string MakeNoise()
{
return "Nyan";
}
}
상속 테스트 및 탐색
interface BaseInterface {}
class BaseClass : BaseInterface {}
interface DerivedInterface {}
class DerivedClass : BaseClass, DerivedInterface {}
var baseInterfaceType = typeof(BaseInterface);
var derivedInterfaceType = typeof(DerivedInterface);
var baseType = typeof(BaseClass);
var derivedType = typeof(DerivedClass);
var baseInstance = new BaseClass();
var derivedInstance = new DerivedClass();
Console.WriteLine(derivedInstance is DerivedClass); //True
Console.WriteLine(derivedInstance is DerivedInterface); //True
Console.WriteLine(derivedInstance is BaseClass); //True
Console.WriteLine(derivedInstance is BaseInterface); //True
Console.WriteLine(derivedInstance is object); //True
Console.WriteLine(derivedType.BaseType.Name); //BaseClass
Console.WriteLine(baseType.BaseType.Name); //Object
Console.WriteLine(typeof(object).BaseType); //null
Console.WriteLine(baseType.IsInstanceOfType(derivedInstance)); //True
Console.WriteLine(derivedType.IsInstanceOfType(baseInstance)); //False
Console.WriteLine(
string.Join(",",
derivedType.GetInterfaces().Select(t => t.Name).ToArray()));
//BaseInterface,DerivedInterface
Console.WriteLine(baseInterfaceType.IsAssignableFrom(derivedType)); //True
Console.WriteLine(derivedInterfaceType.IsAssignableFrom(derivedType)); //True
Console.WriteLine(derivedInterfaceType.IsAssignableFrom(baseType)); //False
추상 기본 클래스 확장
구현을위한 계약으로 설명 할 수있는 인터페이스와 달리 추상 클래스는 확장을위한 계약의 역할을합니다.
추상 클래스는 인스턴스화 할 수 없으므로 확장되어야하며 결과 클래스 (또는 파생 클래스)는 인스턴스화 될 수 있습니다.
추상 클래스는 일반적인 구현을 제공하는 데 사용됩니다.
public abstract class Car
{
public void HonkHorn() {
// Implementation of horn being honked
}
}
public class Mustang : Car
{
// Simply by extending the abstract class Car, the Mustang can HonkHorn()
// If Car were an interface, the HonkHorn method would need to be included
// in every class that implemented it.
}
위의 예는 Car를 확장하는 모든 클래스가 구현과 함께 HonkHorn 메소드를 자동으로 수신하는 방법을 보여줍니다. 즉, 새 자동차를 만드는 모든 개발자는 경적을 울리는 방법에 대해 걱정할 필요가 없습니다.
서브 클래스의 생성자
기본 클래스의 하위 클래스를 만들 때 하위 클래스 생성자의 매개 변수 : base
를 사용하여 기본 클래스를 구성 할 수 있습니다.
class Instrument
{
string type;
bool clean;
public Instrument (string type, bool clean)
{
this.type = type;
this.clean = clean;
}
}
class Trumpet : Instrument
{
bool oiled;
public Trumpet(string type, bool clean, bool oiled) : base(type, clean)
{
this.oiled = oiled;
}
}
계승. 생성자의 호출 순서
우리는 어린이 클래스를 가진 Animal
클래스를 가지고 있다고 생각합니다. Dog
class Animal
{
public Animal()
{
Console.WriteLine("In Animal's constructor");
}
}
class Dog : Animal
{
public Dog()
{
Console.WriteLine("In Dog's constructor");
}
}
기본적으로 모든 클래스는 암시 적으로 Object
클래스를 상속합니다.
위의 코드와 같습니다.
class Animal : Object
{
public Animal()
{
Console.WriteLine("In Animal's constructor");
}
}
Dog
클래스의 인스턴스를 만들 때 부모 클래스의 다른 생성자에 대한 명시 적 호출이 없으면 매개 변수가없는 기본 클래스의 기본 생성자가 호출됩니다 . 우리의 경우, 먼저 Object's
생성자, Animal's
끝, Dog's
생성자라고합니다.
public class Program
{
public static void Main()
{
Dog dog = new Dog();
}
}
출력은
동물의 생성자에서
개 생성자에서
부모의 생성자를 명시 적으로 호출합니다.
위의 예제에서 Dog
클래스 생성자는 Animal
클래스의 기본 생성자를 호출합니다. 원하는 경우 생성자를 호출해야하는지 지정할 수 있습니다. 부모 클래스에 정의 된 모든 생성자를 호출 할 수 있습니다.
이 두 클래스가 있다고 가정 해보십시오.
class Animal
{
protected string name;
public Animal()
{
Console.WriteLine("Animal's default constructor");
}
public Animal(string name)
{
this.name = name;
Console.WriteLine("Animal's constructor with 1 parameter");
Console.WriteLine(this.name);
}
}
class Dog : Animal
{
public Dog() : base()
{
Console.WriteLine("Dog's default constructor");
}
public Dog(string name) : base(name)
{
Console.WriteLine("Dog's constructor with 1 parameter");
Console.WriteLine(this.name);
}
}
여기서 뭐하는거야?
각 클래스에는 2 개의 생성자가 있습니다.
base
는 무엇을 의미합니까?
base
는 부모 클래스에 대한 참조입니다. 이 경우 Dog
클래스의 인스턴스를 만들 때
Dog dog = new Dog();
런타임은 매개 변수없는 생성자 인 Dog()
먼저 호출합니다. 그러나 시신은 즉시 작동하지 않습니다. 생성자의 괄호 뒤에 다음과 같은 호출이 있습니다. base()
. 기본 Dog
생성자를 호출하면 부모의 기본 생성자가 차례로 호출됩니다. 부모 생성자가 실행 된 후 Dog()
생성자 본문이 반환 된 다음 마지막으로 실행됩니다.
출력은 다음과 같습니다.
동물의 기본 생성자
개 기본 생성자
이제 Dog's
생성자를 매개 변수로 호출하면 어떻게 될까요?
Dog dog = new Dog("Rex");
비공개가 아닌 부모 클래스의 멤버는 자식 클래스에 상속됩니다. 즉, Dog
에는 name
필드도 있습니다.
이 경우 우리는 생성자에게 인수를 넘겼습니다. 자신의 차례가되면 (자), name
필드를 초기화 하는 파라미터 를 사용해, 인수를 친 클래스의 생성자에게 건네줍니다.
출력은
Animal's constructor with 1 parameter
Rex
Dog's constructor with 1 parameter
Rex
개요:
모든 객체 생성은 기본 클래스에서 시작됩니다. 상속에서는 계층 구조에있는 클래스가 연결됩니다. 모든 클래스가 Object
에서 파생되므로 모든 객체가 만들어 질 때 호출되는 첫 번째 생성자는 Object
클래스 생성자입니다. 그런 다음 체인의 다음 생성자가 호출되고 모든 객체가 호출 된 후에 만 객체가 만들어집니다.
기본 키워드
- base 키워드는 파생 클래스 내에서 기본 클래스의 멤버에 액세스하는 데 사용됩니다.
- 다른 메소드에 의해 오버라이드 된 기본 클래스의 메소드를 호출하십시오. 파생 클래스의 인스턴스를 만들 때 호출해야하는 기본 클래스 생성자를 지정합니다.
상속 메소드
메서드를 상속 할 수있는 몇 가지 방법이 있습니다.
public abstract class Car
{
public void HonkHorn() {
// Implementation of horn being honked
}
// virtual methods CAN be overridden in derived classes
public virtual void ChangeGear() {
// Implementation of gears being changed
}
// abstract methods MUST be overridden in derived classes
public abstract void Accelerate();
}
public class Mustang : Car
{
// Before any code is added to the Mustang class, it already contains
// implementations of HonkHorn and ChangeGear.
// In order to compile, it must be given an implementation of Accelerate,
// this is done using the override keyword
public override void Accelerate() {
// Implementation of Mustang accelerating
}
// If the Mustang changes gears differently to the implementation in Car
// this can be overridden using the same override keyword as above
public override void ChangeGear() {
// Implementation of Mustang changing gears
}
}
상속 안티 패턴
부적절한 상속
Foo
와 Bar
클래스 두 클래스가 있다고 가정 해 Foo
. Foo
는 Do1
과 Do2
두 가지 기능을 제공합니다. Bar
사용할 필요가 Do1
에서 Foo
하지만 필요하지 않습니다 Do2
또는 요구가 그와 동일 기능 Do2
하지만 완전히 다른 무언가를.
나쁜 방법 : Foo
가상에서 Do2()
를 만들고 Bar
에서 무시하거나 Do2()
에 Bar
에서 throw Exception
을 throw Exception
public class Bar : Foo
{
public override void Do2()
{
//Does something completely different that you would expect Foo to do
//or simply throws new Exception
}
}
좋은 방법
Foo
에서 Do1()
을 꺼내 새로운 클래스 Baz
넣은 다음 Baz
에서 Foo
와 Bar
를 상속하고 Do2()
별도로 구현합니다.
public class Baz
{
public void Do1()
{
// magic
}
}
public class Foo : Baz
{
public void Do2()
{
// foo way
}
}
public class Bar : Baz
{
public void Do2()
{
// bar way or not have Do2 at all
}
}
이제 첫 번째 예제가 좋지 않고 두 번째 예제가 좋은 이유는 다음과 같습니다. 개발자 nr2가 Foo
에서 변경 작업을 수행해야 할 때 Bar
가 Foo
분리 할 수 없으므로 Bar
구현을 중단 할 가능성이 있습니다. 후자의 예제 Foo
와 Bar
commonalty가 Baz
로 옮겨 졌을 때 서로 영향을 미치지 않습니다.
재귀 유형 지정이있는 기본 클래스
재귀 유형 지정자를 사용하는 일반 기본 클래스의 한 번 정의. 각 노드에는 하나의 상위 노드와 여러 하위 노드가 있습니다.
/// <summary>
/// Generic base class for a tree structure
/// </summary>
/// <typeparam name="T">The node type of the tree</typeparam>
public abstract class Tree<T> where T : Tree<T>
{
/// <summary>
/// Constructor sets the parent node and adds this node to the parent's child nodes
/// </summary>
/// <param name="parent">The parent node or null if a root</param>
protected Tree(T parent)
{
this.Parent=parent;
this.Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; private set; }
public List<T> Children { get; private set; }
public bool IsRoot { get { return Parent==null; } }
public bool IsLeaf { get { return Children.Count==0; } }
/// <summary>
/// Returns the number of hops to the root object
/// </summary>
public int Level { get { return IsRoot ? 0 : Parent.Level+1; } }
}
객체의 트리 계층 구조를 정의해야 할 때마다 위의 내용을 다시 사용할 수 있습니다. 트리의 노드 객체는 다음을 사용하여 기본 클래스에서 상속해야합니다.
public class MyNode : Tree<MyNode>
{
// stuff
}
각 노드 클래스는 계층 구조에서의 위치, 부모 객체가 무엇인지뿐만 아니라 자식 객체가 무엇인지를 알고 있습니다. 여러 가지 내장 된 유형은 Control
또는 XmlElement
와 같은 트리 구조를 사용하며 위의 Tree<T>
는 코드에서 모든 유형의 기본 클래스로 사용할 수 있습니다.
예를 들어, 전체 체중이 모든 어린이의 체중에서 계산되는 부분 계층 구조를 만들려면 다음을 수행하십시오.
public class Part : Tree<Part>
{
public static readonly Part Empty = new Part(null) { Weight=0 };
public Part(Part parent) : base(parent) { }
public Part Add(float weight)
{
return new Part(this) { Weight=weight };
}
public float Weight { get; set; }
public float TotalWeight { get { return Weight+Children.Sum((part) => part.TotalWeight); } }
}
다음과 같이 사용된다.
// [Q:2.5] -- [P:4.2] -- [R:0.4]
// \
// - [Z:0.8]
var Q = Part.Empty.Add(2.5f);
var P = Q.Add(4.2f);
var R = P.Add(0.4f);
var Z = Q.Add(0.9f);
// 2.5+(4.2+0.4)+0.9 = 8.0
float weight = Q.TotalWeight;
다른 예는 상대 좌표 프레임의 정의에 있습니다. 이 경우 좌표 프레임의 실제 위치는 모든 상위 좌표 프레임의 위치에 따라 달라집니다.
public class RelativeCoordinate : Tree<RelativeCoordinate>
{
public static readonly RelativeCoordinate Start = new RelativeCoordinate(null, PointF.Empty) { };
public RelativeCoordinate(RelativeCoordinate parent, PointF local_position)
: base(parent)
{
this.LocalPosition=local_position;
}
public PointF LocalPosition { get; set; }
public PointF GlobalPosition
{
get
{
if(IsRoot) return LocalPosition;
var parent_pos = Parent.GlobalPosition;
return new PointF(parent_pos.X+LocalPosition.X, parent_pos.Y+LocalPosition.Y);
}
}
public float TotalDistance
{
get
{
float dist = (float)Math.Sqrt(LocalPosition.X*LocalPosition.X+LocalPosition.Y*LocalPosition.Y);
return IsRoot ? dist : Parent.TotalDistance+dist;
}
}
public RelativeCoordinate Add(PointF local_position)
{
return new RelativeCoordinate(this, local_position);
}
public RelativeCoordinate Add(float x, float y)
{
return Add(new PointF(x, y));
}
}
다음과 같이 사용된다.
// Define the following coordinate system hierarchy
//
// o--> [A1] --+--> [B1] -----> [C1]
// |
// +--> [B2] --+--> [C2]
// |
// +--> [C3]
var A1 = RelativeCoordinate.Start;
var B1 = A1.Add(100, 20);
var B2 = A1.Add(160, 10);
var C1 = B1.Add(120, -40);
var C2 = B2.Add(80, -20);
var C3 = B2.Add(60, -30);
double dist1 = C1.TotalDistance;