diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index baf3ec3613e82..d41c3b8e477ef 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -2164,6 +2164,11 @@ "redirect_url": "/dotnet/standard/linq/xdocument-class-overview", "redirect_document_id": true }, + { + "source_path": "docs/csharp/programming-guide/concepts/object-oriented-programming.md", + "redirect_url": "/dotnet/csharp/tutorials/intro-to-csharp/object-oriented-programming", + "redirect_document_id": true + }, { "source_path": "docs/csharp/programming-guide/concepts/linq/xelement-class-overview.md", "redirect_url": "/dotnet/standard/linq/xelement-class-overview", diff --git a/docs/csharp/programming-guide/classes-and-structs/classes.md b/docs/csharp/programming-guide/classes-and-structs/classes.md index dbbc1c661e23d..832d2ddecedde 100644 --- a/docs/csharp/programming-guide/classes-and-structs/classes.md +++ b/docs/csharp/programming-guide/classes-and-structs/classes.md @@ -96,7 +96,7 @@ The following example defines a public class that contains an [auto-implemented ## See also - [C# Programming Guide](../index.md) -- [Object-Oriented Programming](../concepts/object-oriented-programming.md) +- [Object-Oriented Programming](../../tutorials/intro-to-csharp/object-oriented-programming.md) - [Polymorphism](polymorphism.md) - [Identifier names](../inside-a-program/identifier-names.md) - [Members](members.md) diff --git a/docs/csharp/programming-guide/concepts/index.md b/docs/csharp/programming-guide/concepts/index.md index 4a6398fa33690..48bfb32c5bde9 100644 --- a/docs/csharp/programming-guide/concepts/index.md +++ b/docs/csharp/programming-guide/concepts/index.md @@ -19,7 +19,6 @@ This section explains programming concepts in the C# language. |[Expression Trees (C#)](./expression-trees/index.md)|Explains how you can use expression trees to enable dynamic modification of executable code.| |[Iterators (C#)](./iterators.md)|Describes iterators, which are used to step through collections and return elements one at a time.| |[Language-Integrated Query (LINQ) (C#)](./linq/index.md)|Discusses the powerful query capabilities in the language syntax of C#, and the model for querying relational databases, XML documents, datasets, and in-memory collections.| -|[Object-Oriented Programming (C#)](./object-oriented-programming.md)|Describes common object-oriented concepts, including encapsulation, inheritance, and polymorphism.| |[Reflection (C#)](./reflection.md)|Explains how to use reflection to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object and invoke its methods or access its fields and properties.| |[Serialization (C#)](./serialization/index.md)|Describes key concepts in binary, XML, and SOAP serialization.| diff --git a/docs/csharp/programming-guide/concepts/object-oriented-programming.md b/docs/csharp/programming-guide/concepts/object-oriented-programming.md deleted file mode 100644 index f58df5bc3f79b..0000000000000 --- a/docs/csharp/programming-guide/concepts/object-oriented-programming.md +++ /dev/null @@ -1,401 +0,0 @@ ---- -title: "Object-Oriented Programming (C#)" -description: C# provides full support for object-oriented programming including abstraction, encapsulation, inheritance, and polymorphism. -ms.date: 05/13/2020 -ms.assetid: 89574786-65ef-4335-88bc-fbacd094f183 ---- -# Object-Oriented programming (C#) - -C# provides full support for object-oriented programming including abstraction, encapsulation, inheritance, and polymorphism. - -- *Abstraction* means hiding the unnecessary details from type consumers. -- *Encapsulation* means that a group of related properties, methods, and other members are treated as a single unit or object. -- *Inheritance* describes the ability to create new classes based on an existing class. -- *Polymorphism* means that you can have multiple classes that can be used interchangeably, even though each class implements the same properties or methods in different ways. - -## Classes and objects - -The terms *class* and *object* describe the *type* of objects, and the *instances* of classes, respectively. So, the act of creating an object is called *instantiation*. Using the blueprint analogy, a class is a blueprint, and an object is a building made from that blueprint. - -To define a class: - -```csharp -class SampleClass -{ -} -``` - -C# also provides types called *structures* that are useful when you don't need support for inheritance or polymorphism. For more information, see [Choosing between class and struct](../../../standard/design-guidelines/choosing-between-class-and-struct.md). - -To define a structure: - -```csharp -struct SampleStruct -{ -} -``` - -For more information, see the articles on the [class](../../language-reference/keywords/class.md) and [struct](../../language-reference/builtin-types/struct.md) keywords. - -### Class members - -Each class can have different *class members* that include properties that describe class data, methods that define class behavior, and events that provide communication between different classes and objects. - -#### Properties and fields - -Fields and properties represent information that an object contains. Fields are like variables because they can be read or set directly, subject to applicable access modifiers. - -To define a field that can be accessed from within instances of the class: - -```csharp -public class SampleClass -{ - string sampleField; -} -``` - -Properties have `get` and `set` accessors, which provide more control on how values are set or returned. - -C# allows you either to create a private field for storing the property value or use auto-implemented properties that create this field automatically behind the scenes and provide the basic logic for the property procedures. - -To define an auto-implemented property: - -```csharp -class SampleClass -{ - public int SampleProperty { get; set; } -} -``` - -If you need to perform some additional operations for reading and writing the property value, define a field for storing the property value and provide the basic logic for storing and retrieving it: - -```csharp -class SampleClass -{ - private int _sample; - public int Sample - { - // Return the value stored in a field. - get => _sample; - // Store the value in the field. - set => _sample = value; - } -} -``` - -Most properties have methods or procedures to both set and get the property value. However, you can create read-only or write-only properties to restrict them from being modified or read. In C#, you can omit the `get` or `set` property method. However, auto-implemented properties cannot be write-only. Read-only auto-implemented properties can be set in constructors of the containing class. - -For more information, see: - -- [get](../../language-reference/keywords/get.md) -- [set](../../language-reference/keywords/set.md) - -#### Methods - -A *method* is an action that an object can perform. - -To define a method of a class: - -```csharp -class SampleClass -{ - public int SampleMethod(string sampleParam) - { - // Insert code here - } -} -``` - -A class can have several implementations, or *overloads*, of the same method that differ in the number of parameters or parameter types. - -To overload a method: - -```csharp -public int SampleMethod(string sampleParam) { } -public int SampleMethod(int sampleParam) { } -``` - -In most cases you declare a method within a class definition. However, C# also supports *extension methods* that allow you to add methods to an existing class outside the actual definition of the class. - -For more information, see: - -- [Methods](../classes-and-structs/methods.md) -- [Extension Methods](../classes-and-structs/extension-methods.md) - -#### Constructors - -Constructors are class methods that are executed automatically when an object of a given type is created. Constructors usually initialize the data members of the new object. A constructor can run only once when a class is created. Furthermore, the code in the constructor always runs before any other code in a class. However, you can create multiple constructor overloads in the same way as for any other method. - -To define a constructor for a class: - -```csharp -public class SampleClass -{ - public SampleClass() - { - // Add code here - } -} -``` - -For more information, see [Constructors](../classes-and-structs/constructors.md). - -#### Finalizers - -A finalizer is used to destruct instances of classes. In .NET, the garbage collector automatically manages the allocation and release of memory for the managed objects in your application. However, you may still need finalizers to clean up any unmanaged resources that your application creates. There can be only one finalizer for a class. - -For more information about finalizers and garbage collection in .NET, see [Garbage Collection](../../../standard/garbage-collection/index.md). - -#### Events - -Events enable a class or object to notify other classes or objects when something of interest occurs. The class that sends (or raises) the event is called the *publisher* and the classes that receive (or handle) the event are called *subscribers*. For more information about events, how they are raised and handled, see [Events](../../../standard/events/index.md). - -- To declare an event in a class, use the [event](../../language-reference/keywords/event.md) keyword. -- To raise an event, invoke the event delegate. -- To subscribe to an event, use the `+=` operator; to unsubscribe from an event, use the `-=` operator. - -#### Nested classes - -A class defined within another class is called *nested*. By default, the nested class is private. - -```csharp -class Container -{ - class Nested - { - // Add code here. - } -} -``` - -To create an instance of the nested class, use the name of the container class followed by the dot and then followed by the name of the nested class: - -```csharp -Container.Nested nestedInstance = new Container.Nested() -``` - -### Access modifiers and access levels - -All classes and class members can specify what access level they provide to other classes by using *access modifiers*. - -The following access modifiers are available: - -| C# Modifier | Definition | -|--|--| -| [public](../../language-reference/keywords/public.md) | The type or member can be accessed by any other code in the same assembly or another assembly that references it. | -| [private](../../language-reference/keywords/private.md) | The type or member can only be accessed by code in the same class. | -| [protected](../../language-reference/keywords/protected.md) | The type or member can only be accessed by code in the same class or in a derived class. | -| [internal](../../language-reference/keywords/internal.md) | The type or member can be accessed by any code in the same assembly, but not from another assembly. | -| [protected internal](../../language-reference/keywords/protected-internal.md) | The type or member can be accessed by any code in the same assembly, or by any derived class in another assembly. | -| [private protected](../../language-reference/keywords/private-protected.md) | The type or member can be accessed by code in the same class or in a derived class within the base class assembly. | - -For more information, see [Access Modifiers](../classes-and-structs/access-modifiers.md). - -### Instantiating classes - -To create an object, you need to instantiate a class, or create a class instance. - -```csharp -SampleClass sampleObject = new SampleClass(); -``` - -After instantiating a class, you can assign values to the instance's properties and fields and invoke class methods. - -```csharp -// Set a property value. -sampleObject.sampleProperty = "Sample String"; -// Call a method. -sampleObject.SampleMethod(); -``` - -To assign values to properties during the class instantiation process, use object initializers: - -```csharp -// Set a property value. -var sampleObject = new SampleClass -{ - FirstProperty = "A", - SecondProperty = "B" -}; -``` - -For more information, see: - -- [new Operator](../../language-reference/operators/new-operator.md) -- [Object and Collection Initializers](../classes-and-structs/object-and-collection-initializers.md) - -### Static Classes and Members - -A static member of the class is a property, procedure, or field that is shared by all instances of a class. - -To define a static member: - -```csharp -static class SampleClass -{ - public static string SampleString = "Sample String"; -} -``` - -To access the static member, use the name of the class without creating an object of this class: - -```csharp -Console.WriteLine(SampleClass.SampleString); -``` - -Static classes in C# have static members only and cannot be instantiated. Static members also cannot access non-static properties, fields or methods - -For more information, see: [static](../../language-reference/keywords/static.md). - -### Anonymous types - -Anonymous types enable you to create objects without writing a class definition for the data type. Instead, the compiler generates a class for you. The class has no usable name and contains the properties you specify in declaring the object. - -To create an instance of an anonymous type: - -```csharp -// sampleObject is an instance of a simple anonymous type. -var sampleObject = new -{ - FirstProperty = "A", - SecondProperty = "B" -}; -``` - -For more information, see: [Anonymous Types](../classes-and-structs/anonymous-types.md). - -## Inheritance - -Inheritance enables you to create a new class that reuses, extends, and modifies the behavior that is defined in another class. The class whose members are inherited is called the *base class*, and the class that inherits those members is called the *derived class*. However, all classes in C# implicitly inherit from the class that supports .NET class hierarchy and provides low-level services to all classes. - -> [!NOTE] -> C# doesn't support multiple inheritance. That is, you can specify only one base class for a derived class. - -To inherit from a base class: - -```csharp -class DerivedClass:BaseClass { } -``` - -By default all classes can be inherited. However, you can specify whether a class must not be used as a base class, or create a class that can be used as a base class only. - -To specify that a class cannot be used as a base class: - -```csharp -public sealed class A { } -``` - -To specify that a class can be used as a base class only and cannot be instantiated: - -```csharp -public abstract class B { } -``` - -For more information, see: - -- [sealed](../../language-reference/keywords/sealed.md) -- [abstract](../../language-reference/keywords/abstract.md) - -### Overriding Members - -By default, a derived class inherits all members from its base class. If you want to change the behavior of the inherited member, you need to override it. That is, you can define a new implementation of the method, property or event in the derived class. - -The following modifiers are used to control how properties and methods are overridden: - -| C# Modifier | Definition | -|--|--| -| [virtual](../../language-reference/keywords/virtual.md) | Allows a class member to be overridden in a derived class. | -| [override](../../language-reference/keywords/override.md) | Overrides a virtual (overridable) member defined in the base class. | -| [abstract](../../language-reference/keywords/abstract.md) | Requires that a class member to be overridden in the derived class. | -| [new Modifier](../../language-reference/keywords/new-modifier.md) | Hides a member inherited from a base class | - -## Interfaces - -Interfaces, like classes, define a set of properties, methods, and events. But unlike classes, interfaces do not provide implementation. They are implemented by classes, and defined as separate entities from classes. An interface represents a contract, in that a class that implements an interface must implement every aspect of that interface exactly as it is defined. - -To define an interface: - -```csharp -interface ISampleInterface -{ - void DoSomething(); -} -``` - -To implement an interface in a class: - -```csharp -class SampleClass : ISampleInterface -{ - void ISampleInterface.DoSomething() - { - // Method implementation. - } -} -``` - -For more information, see the programming guide article on [Interfaces](../interfaces/index.md) and the language reference article on the [interface](../../language-reference/keywords/interface.md) keyword. - -## Generics - -Classes, structures, interfaces, and methods in .NET can include *type parameters* that define types of objects that they can store or use. The most common example of generics is a collection, where you can specify the type of objects to be stored in a collection. - -To define a generic class: - -```csharp -public class SampleGeneric -{ - public T Field; -} -``` - -To create an instance of a generic class: - -```csharp -var sampleObject = new SampleGeneric(); -sampleObject.Field = "Sample string"; -``` - -For more information, see: - -- [Generics in .NET](../../../standard/generics/index.md) -- [Generics - C# Programming Guide](../generics/index.md) - -## Delegates - -A *delegate* is a type that defines a method signature, and can provide a reference to any method with a compatible signature. You can invoke (or call) the method through the delegate. Delegates are used to pass methods as arguments to other methods. - -> [!NOTE] -> Event handlers are nothing more than methods that are invoked through delegates. For more information about using delegates in event handling, see [Events](../../../standard/events/index.md). - -To create a delegate: - -```csharp -public delegate void SampleDelegate(string str); -``` - -To create a reference to a method that matches the signature specified by the delegate: - -```csharp -class SampleClass -{ - // Method that matches the SampleDelegate signature. - public static void SampleMethod(string message) - { - // Add code here. - } - - // Method that instantiates the delegate. - void SampleDelegate() - { - SampleDelegate sd = sampleMethod; - sd("Sample string"); - } -} -``` - -For more information, see the programming guide article on [Delegates](../delegates/index.md) and the language reference article on the [delegate](../../language-reference/builtin-types/reference-types.md) keyword. - -## See also - -- [C# Programming Guide](../index.md) diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index d6e84314a3813..3fbb555e8970a 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -36,6 +36,8 @@ href: tutorials/intro-to-csharp/arrays-and-collections.md - name: Introduction to classes href: tutorials/intro-to-csharp/introduction-to-classes.md + - name: Object-Oriented programming + href: tutorials/intro-to-csharp/object-oriented-programming.md - name: Explore C# 6 href: tutorials/exploration/csharp-6.yml - name: Explore string interpolation - interactive @@ -510,8 +512,6 @@ href: programming-guide/concepts/linq/enabling-a-data-source-for-linq-querying1.md - name: Visual Studio IDE and Tools Support for LINQ href: programming-guide/concepts/linq/visual-studio-ide-and-tools-support-for-linq.md - - name: Object-Oriented Programming - href: programming-guide/concepts/object-oriented-programming.md - name: Reflection href: programming-guide/concepts/reflection.md - name: Serialization (C#) diff --git a/docs/csharp/tutorials/intro-to-csharp/index.md b/docs/csharp/tutorials/intro-to-csharp/index.md index 2bf59b2a0a7f1..979fe3669425f 100644 --- a/docs/csharp/tutorials/intro-to-csharp/index.md +++ b/docs/csharp/tutorials/intro-to-csharp/index.md @@ -69,7 +69,13 @@ This tutorial assumes that you've finished the lessons listed above. ## [Introduction to classes](introduction-to-classes.md) -This final tutorial is only available to run on your machine, using your own local development environment and .NET Core. +This tutorial is only available to run on your machine, using your own local development environment and .NET Core. You'll build a console application and see the basic object-oriented features that are part of the C# language. This tutorial assumes you've finished the online introductory tutorials, and you've installed [.NET Core SDK](https://dotnet.microsoft.com/download) and [Visual Studio Code](https://code.visualstudio.com/). + +## [Object oriented programming](object-oriented-programming.md) + +This tutorial teaches the concepts used in object-oriented programming. You'll learn the concepts of *abstraction*, *encapsulation*, *inheritance*, and *polymorphism* using C# examples. + +This tutorial assumes you've finished the online introductory tutorials, and you've installed [.NET Core SDK](https://dotnet.microsoft.com/download) and either [Visual Studio Code](https://code.visualstudio.com/) or [Visual Studio](https://visualstudio.com) on your development machine. diff --git a/docs/csharp/tutorials/intro-to-csharp/introduction-to-classes.md b/docs/csharp/tutorials/intro-to-csharp/introduction-to-classes.md index 9406caddc739a..07a5ef05caec5 100644 --- a/docs/csharp/tutorials/intro-to-csharp/introduction-to-classes.md +++ b/docs/csharp/tutorials/intro-to-csharp/introduction-to-classes.md @@ -115,11 +115,11 @@ Your bank account class needs to accept deposits and withdrawals to work correct Let's start by creating a new type to represent a transaction. This is a simple type that doesn't have any responsibilities. It needs a few properties. Create a new file named *Transaction.cs*. Add the following code to it: -[!code-csharp[Transaction](~/samples/snippets/csharp/classes-quickstart/Transaction.cs)] +:::code language="csharp" source="./snippets/introduction-to-classes/Transaction.cs"::: Now, let's add a of `Transaction` objects to the `BankAccount` class. Add the following declaration after the constructor in your *BankAccount.cs* file: -[!code-csharp[TransactionDecl](~/samples/snippets/csharp/classes-quickstart/BankAccount.cs#TransactionDeclaration)] +:::code language="csharp" source="./snippets/introduction-to-classes/BankAccount.cs" id="TransactionDeclaration"::: The class requires you to import a different namespace. Add the following at the beginning of *BankAccount.cs*: @@ -129,7 +129,7 @@ using System.Collections.Generic; Now, let's change how the `Balance` is reported. It can be found by summing the values of all transactions. Modify the declaration of `Balance` in the `BankAccount` class to the following: -[!code-csharp[BalanceComputation](~/samples/snippets/csharp/classes-quickstart/BankAccount.cs#BalanceComputation)] +:::code language="csharp" source="./snippets/introduction-to-classes/BankAccount.cs" id="BalanceComputation"::: This example shows an important aspect of ***properties***. You're now computing the balance when another programmer asks for the value. Your computation enumerates all transactions, and provides the sum as the current balance. @@ -137,13 +137,13 @@ Next, implement the `MakeDeposit` and `MakeWithdrawal` methods. These methods wi This introduces the concept of ***exceptions***. The standard way of indicating that a method cannot complete its work successfully is to throw an exception. The type of exception and the message associated with it describe the error. Here, the `MakeDeposit` method throws an exception if the amount of the deposit is negative. The `MakeWithdrawal` method throws an exception if the withdrawal amount is negative, or if applying the withdrawal results in a negative balance. Add the following code after the declaration of the `allTransactions` list: -[!code-csharp[DepositAndWithdrawal](~/samples/snippets/csharp/classes-quickstart/BankAccount.cs#DepositAndWithdrawal)] +:::code language="csharp" source="./snippets/introduction-to-classes/BankAccount.cs" id="DepositAndWithdrawal"::: The [`throw`](../../language-reference/keywords/throw.md) statement **throws** an exception. Execution of the current block ends, and control transfers to the first matching `catch` block found in the call stack. You'll add a `catch` block to test this code a little later on. The constructor should get one change so that it adds an initial transaction, rather than updating the balance directly. Since you already wrote the `MakeDeposit` method, call it from your constructor. The finished constructor should look like this: -[!code-csharp[Constructor](~/samples/snippets/csharp/classes-quickstart/BankAccount.cs#Constructor)] +:::code language="csharp" source="./snippets/introduction-to-classes/BankAccount.cs" id="Constructor"::: is a property that returns the current date and time. Test this by adding a few deposits and withdrawals in your `Main` method, following the code that creates a new `BankAccount`: @@ -190,7 +190,7 @@ Save the file and type `dotnet run` to try it. To finish this tutorial, you can write the `GetAccountHistory` method that creates a `string` for the transaction history. Add this method to the `BankAccount` type: -[!code-csharp[History](~/samples/snippets/csharp/classes-quickstart/BankAccount.cs#History)] +:::code language="csharp" source="./snippets/introduction-to-classes/BankAccount.cs" id="History"::: This uses the class to format a string that contains one line for each transaction. You've seen the string formatting code earlier in these tutorials. One new character is `\t`. That inserts a tab to format the output. @@ -206,4 +206,11 @@ Run your program to see the results. If you got stuck, you can see the source for this tutorial [in our GitHub repo](https://github.com/dotnet/docs/tree/master/samples/snippets/csharp/classes-quickstart/). -Congratulations, you've finished all our introduction to C# tutorials. If you're eager to learn more, try more of our [tutorials](../index.md). +You can continue with the [object oriented programming](object-oriented-programming.md) tutorial. + +You can learn more about these concepts in these articles: + +- [If and else statement](../../language-reference/keywords/if-else.md) +- [While statement](../../language-reference/keywords/while.md) +- [Do statement](../../language-reference/keywords/do.md) +- [For statement](../../language-reference/keywords/for.md) diff --git a/docs/csharp/tutorials/intro-to-csharp/object-oriented-programming.md b/docs/csharp/tutorials/intro-to-csharp/object-oriented-programming.md new file mode 100644 index 0000000000000..b7916154cf45f --- /dev/null +++ b/docs/csharp/tutorials/intro-to-csharp/object-oriented-programming.md @@ -0,0 +1,178 @@ +--- +title: "Object-Oriented Programming (C#)" +description: C# provides full support for object-oriented programming including abstraction, encapsulation, inheritance, and polymorphism. +ms.date: 09/30/2020 +--- +# Object-Oriented programming (C#) + +C# is an object-oriented language. Four of the key techniques used in object-oriented programming are: + +- *Abstraction* means hiding the unnecessary details from type consumers. +- *Encapsulation* means that a group of related properties, methods, and other members are treated as a single unit or object. +- *Inheritance* describes the ability to create new classes based on an existing class. +- *Polymorphism* means that you can have multiple classes that can be used interchangeably, even though each class implements the same properties or methods in different ways. + +In the preceding tutorial, [introduction to classes](introduction-to-classes.md) you saw both *abstraction* and *encapsulation*. The `BankAccount` class provided an abstraction for the concept of a bank account. You could modify its implementation without affecting any of the code that used the `BankAccount` class. Both the `BankAccount` and `Transaction` classes provide encapsulation of the components needed to describe those concepts in code. + +In this tutorial, you'll extend that application to make use of *inheritance* and *polymorphism* to add new features. You'll also add features to the `BankAccount` class, taking advantage of the *abstraction* and *encapsulation* techniques you learned in the preceding tutorial. + +## Create different types of accounts + +After building this program, you get requests to add features to it. It works great in the situation where there is only one bank account type. Over time, needs change, and related account types are requested: + +- An interest earning account that accrues interest at the end of each month. +- A line of credit that can have a negative balance, but when there's a balance, there's an interest charge each month. +- A pre-paid gift card account that starts with a single deposit, and only can be paid off. It can be refilled once at the start of each month. + +All of these different accounts are similar to `BankAccount` class defined in the earlier tutorial. You could copy that code, rename the classes, and make modifications. That technique would work in the short term, but it would be more work over time. Any changes would be copied across all the affected classes. + +Instead, you can create new bank account types that inherit methods and data from the `BankAccount` class created in the preceding tutorial. These new classes can extend the `BankAccount` class with the specific behavior needed for each type: + +```csharp +public class InterestEarningAccount : BankAccount +{ +} + +public class LineOfCreditAccount : BankAccount +{ +} + +public class GiftCardAccount : BankAccount +{ +} +``` + +Each of these classes *inherits* the shared behavior from their shared *base class*, the `BankAccount` class. Write the implementations for new and different functionality in each of the *derived classes*. These derived classes already have all the behavior defined in the `BankAccount` class. + +It's a good practice to create each new class in a different source file. In [Visual Studio](https://visualstudio.com), you can right-click on the project, and select *add class* to add a new class in a new file. In [Visual Studio Code](https://code.visualstudio.com), select *File* then *New* to create a new source file. In either tool, name the file to match the class: *InterestEarningAccount.cs*, *LineOfCreditAccount.cs*, and *GiftCardAccount.cs*. + +When you create the classes as shown in the preceding sample, you'll find that none of your derived classes compile. A constructor is responsible for initializing an object. A derived class constructor must initialize the derived class, and provide instructions on how to initialize the base class object included in the derived class. The proper initialization normally happens without any extra code. The `BankAccount` class declares one public constructor with the following signature: + +```csharp +public BankAccount(string name, decimal initialBalance) +``` + +The compiler doesn't generate a default constructor when you define a constructor yourself. That means each derived class must explicitly call this constructor. You declare a constructor that can pass arguments to the base class constructor. The following code shows the constructor for the `InterestEarningAccount`: + +:::code language="csharp" source="./snippets/object-oriented-programming/InterestEarningAccount.cs" ID="DerivedConstructor"::: + +The parameters to this new constructor match the parameter type and names of the base class constructor. You use the `: base()` syntax to indicate a call to a base class constructor. Some classes define multiple constructors, and this syntax enables you to pick which base class constructor you call. Once you've updated the constructors, you can develop the code for each of the derived classes. The requirements for the new classes can be stated as follows: + +- An interest earning account: + - Will get a credit of 2% of the month-ending-balance. +- A line of credit: + - Can have a negative balance, but not be greater in absolute value than the credit limit. + - Will incur an interest charge each month where the end of month balance isn't 0. + - Will incur a fee on each withdrawal that goes over the credit limit. +- A gift card account: + - Can be refilled with a specified amount once each month, on the last day of the month. + +You can see that all three of these account types have an action that takes places at the end of each month. However, each account type does different tasks. You use *polymorphism* to implement this code. Create a single `virtual` method in the `BankAccount` class: + +:::code language="csharp" source="./snippets/object-oriented-programming/BankAccount.cs" ID="DeclareMonthEndTransactions"::: + +The preceding code shows how you use the `virtual` keyword to declare a method in the base class that a derived class may provide a different implementation for. A `virtual` method is a method where any derived class may choose to reimplement. The derived classes use the `override` keyword to define the new implementation. Typically you refer to this as "overriding the base class implementation". The `virtual` keyword specifies that derived classes may override the behavior. You can also declare `abstract` methods where derived classes must override the behavior. The base class does not provide an implementation for an `abstract` method. Next, you need to define the implementation for two of the new classes you've created. Start with the `InterestEarningAccount`: + +:::code language="csharp" source="./snippets/object-oriented-programming/InterestEarningAccount.cs" ID="ApplyMonthendInterest"::: + +Add the following code to the `LineOfCreditAccount`. The code negates the balance to compute a positive interest charge that is withdrawn from the account: + +:::code language="csharp" source="./snippets/object-oriented-programming/LineOfCreditAccount.cs" ID="ApplyMonthendInterest"::: + +The `GiftCardAccount` class needs two changes to implement its month-end functionality. First, modify the constructor to include an optional amount to add each month: + +:::code language="csharp" source="./snippets/object-oriented-programming/GiftCardAccount.cs" ID="GiftCardAccountConstruction"::: + +The constructor provides a default value for the `monthlyDeposit` value so callers can omit a `0` for no monthly deposit. Next, override the `PerformMonthEndTransactions` method to add the monthly deposit, if it was set to a non-zero value in the constructor: + +:::code language="csharp" source="./snippets/object-oriented-programming/GiftCardAccount.cs" ID="AddMonthlyDeposit"::: + +The override applies the monthly deposit set in the constructor. Add the following code to the `Main` method to test these changes for the `GiftCardAccount` and the `InterestEarningAccount`: + +:::code language="csharp" source="./snippets/object-oriented-programming/Program.cs" ID="FirstTests"::: + +Verify the results. Now, add a similar set of test code for the `LineOfCreditAccount`: + +:::code language="csharp" source="./snippets/object-oriented-programming/Program.cs" ID="TestLineOfCredit"::: + +When you add the preceding code and run the program, you'll see something like the following error: + +```console +Unhandled exception. System.ArgumentOutOfRangeException: Amount of deposit must be positive (Parameter 'amount') + at OOProgramming.BankAccount.MakeDeposit(Decimal amount, DateTime date, String note) in BankAccount.cs:line 42 + at OOProgramming.BankAccount..ctor(String name, Decimal initialBalance) in BankAccount.cs:line 31 + at OOProgramming.LineOfCreditAccount..ctor(String name, Decimal initialBalance) in LineOfCreditAccount.cs:line 9 + at OOProgramming.Program.Main(String[] args) in Program.cs:line 29 +``` + +> [!NOTE] +> The actual output includes the full path to the folder with the project. The folder names were omitted for brevity. Also, depending on your code format, the line numbers may be slightly different. + +This code fails because the `BankAccount` assumes that the initial balance must be greater than 0. Another assumption baked into the `BankAccount` class is that the balance can't go negative. Instead, any withdrawal that overdraws the account is rejected. Both of those assumptions need to change. The line of credit account starts at 0, and generally will have a negative balance. Also, if a customer borrows too much money, they incur a fee. The transaction is accepted, it just costs more. The first rule can be implemented by adding an optional argument to the `BankAccount` constructor that specifies the minimum balance. The default is `0`. The second rule requires a mechanism that enables derived classes to modify the default algorithm. In a sense, the base class "asks" the derived type what should happen when there's an overdraft. The default behavior is to reject the transaction by throwing an exception. + +Let's start by adding a second constructor that includes an optional `minimumBalance` parameter. This new constructor does all the actions done by the existing constructor. Also, it sets the minimum balance property. You could copy the body of the existing constructor. but that means two locations to change in the future. Instead, you can use *constructor chaining* to have one constructor call another. The following code shows the two constructors and the new additional field: + + :::code language="csharp" source="./snippets/object-oriented-programming/BankAccount.cs" ID="ConstructorModifications"::: + +The preceding code shows two new techniques. First, the `minimumBalance` field is marked as `readonly`. That means the value cannot be changed after the object is constructed. Once a `BankAccount` is created, the `minimumBalance` can't change. Second, the constructor that takes two parameters uses `: this(name, initialBalance, 0) { }` as its implementation. The `: this()` expression calls the other constructor, the one with three parameters. This technique allows you to have a single implementation for initializing an object even though client code can choose one of many constructors. + +This implementation calls `MakeDeposit` only if the initial balance is greater than `0`. That preserves the rule that deposits must be positive, yet lets the credit account open with a `0` balance. + +Now that the `BankAccount` class has a read-only field for the minimum balance, the final change is to change the hard code `0` to `minimumBalance` in the `MakeWithdrawal` method: + +```csharp +if (Balance - amount < minimumBalance) +``` + +After extending the `BankAccount` class, you can modify the `LineOfCreditAccount` constructor to call the new base constructor, as shown in the following code: + +:::code language="csharp" source="./snippets/object-oriented-programming/LineOfCreditAccount.cs" ID="ConstructLineOfCredit"::: + +Notice that the `LineOfCreditAccount` constructor changes the sign of the `creditLimit` parameter so it matches the meaning of the `minimumBalance` parameter. + +## Different overdraft rules + +The last feature to add enables the `LineOfCreditAccount` to charge a fee for going over the credit limit instead of refusing the transaction. + +One technique is to define a virtual function where you implement the required behavior. The `Bank Account` class refactors the `MakeWithdrawal` method into two methods. The new method does the specified action when the withdrawal takes the balance below the minimum. The existing `MakeWithdrawal` method has the following code: + +```csharp +public void MakeWithdrawal(decimal amount, DateTime date, string note) +{ + if (amount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive"); + } + if (Balance - amount < minimumBalance) + { + throw new InvalidOperationException("Not sufficient funds for this withdrawal"); + } + var withdrawal = new Transaction(-amount, date, note); + allTransactions.Add(withdrawal); +} +``` + +Replace it with the following code: + +:::code language="csharp" source="./snippets/object-oriented-programming/BankAccount.cs" ID="RefactoredMakeWithdrawal"::: + +The added method is , which means that it can be called only from derived classes. That declaration prevents other clients from calling the method. It's also `virtual` so that derived classes can change the behavior. The return type is a `Transaction?`. The `?` annotation indicates that the method may return `null`. Add the following implementation in the `LineOfCreditAccount` to charge a fee when the withdrawal limit is exceeded: + +:::code language="csharp" source="./snippets/object-oriented-programming/LineOfCreditAccount.cs" ID="AddOverdraftFee"::: + +The override returns a fee transaction when the account is overdrawn. If the withdrawal doesn't go over the limit, the method returns a `null` transaction. That indicates there's no fee. Test these changes by adding the following code to your `Main` method in the `Program` class: + +:::code language="csharp" source="./snippets/object-oriented-programming/Program.cs" ID="TestLineOfCredit"::: + +Run the program, and check the results. + +## Summary + +This tutorial demonstrated many of the techniques used in Object-Oriented programming: + +- You used *Abstraction* when you kept many details `private` in each class. +- You used *Encapsulation* when you defined classes for each of the different account types. Those classes described the behavior for that type of account. +- You used *Inheritance* when you leveraged the implementation already created in the `BankAccount` class to save code. +- You used *Polymorphism* when you created `virtual` methods that derived classes could override to create specific behavior for that account type. + +Congratulations, you've finished all of our introduction to C# tutorials. To learn more, try more of our [tutorials](../index.md). diff --git a/samples/snippets/csharp/classes-quickstart/BankAccount.cs b/docs/csharp/tutorials/intro-to-csharp/snippets/introduction-to-classes/BankAccount.cs similarity index 100% rename from samples/snippets/csharp/classes-quickstart/BankAccount.cs rename to docs/csharp/tutorials/intro-to-csharp/snippets/introduction-to-classes/BankAccount.cs diff --git a/samples/snippets/csharp/classes-quickstart/Program.cs b/docs/csharp/tutorials/intro-to-csharp/snippets/introduction-to-classes/Program.cs similarity index 100% rename from samples/snippets/csharp/classes-quickstart/Program.cs rename to docs/csharp/tutorials/intro-to-csharp/snippets/introduction-to-classes/Program.cs diff --git a/samples/snippets/csharp/classes-quickstart/classes.csproj b/docs/csharp/tutorials/intro-to-csharp/snippets/introduction-to-classes/classes.csproj similarity index 100% rename from samples/snippets/csharp/classes-quickstart/classes.csproj rename to docs/csharp/tutorials/intro-to-csharp/snippets/introduction-to-classes/classes.csproj diff --git a/samples/snippets/csharp/classes-quickstart/transaction.cs b/docs/csharp/tutorials/intro-to-csharp/snippets/introduction-to-classes/transaction.cs similarity index 100% rename from samples/snippets/csharp/classes-quickstart/transaction.cs rename to docs/csharp/tutorials/intro-to-csharp/snippets/introduction-to-classes/transaction.cs diff --git a/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/BankAccount.cs b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/BankAccount.cs new file mode 100644 index 0000000000000..cd99e3af442cd --- /dev/null +++ b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/BankAccount.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; + +namespace OOProgramming +{ + public class BankAccount + { + public string Number { get; } + public string Owner { get; set; } + public decimal Balance + { + get + { + decimal balance = 0; + foreach (var item in allTransactions) + { + balance += item.Amount; + } + + return balance; + } + } + + private static int accountNumberSeed = 1234567890; + + // + private readonly decimal minimumBalance; + + public BankAccount(string name, decimal initialBalance) : this(name, initialBalance, 0) { } + + public BankAccount(string name, decimal initialBalance, decimal minimumBalance) + { + this.Number = accountNumberSeed.ToString(); + accountNumberSeed++; + + this.Owner = name; + this.minimumBalance = minimumBalance; + if (initialBalance > 0) + MakeDeposit(initialBalance, DateTime.Now, "Initial balance"); + } + // + + private List allTransactions = new List(); + + public void MakeDeposit(decimal amount, DateTime date, string note) + { + if (amount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive"); + } + var deposit = new Transaction(amount, date, note); + allTransactions.Add(deposit); + } + + // + public void MakeWithdrawal(decimal amount, DateTime date, string note) + { + if (amount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive"); + } + var overdraftTransaction = CheckWithdrawalLimit(Balance - amount < minimumBalance); + var withdrawal = new Transaction(-amount, date, note); + allTransactions.Add(withdrawal); + if (overdraftTransaction != null) + allTransactions.Add(overdraftTransaction); + } + + protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn) + { + if (isOverdrawn) + { + throw new InvalidOperationException("Not sufficient funds for this withdrawal"); + } + else + { + return default; + } + } + // + + public string GetAccountHistory() + { + var report = new System.Text.StringBuilder(); + + decimal balance = 0; + report.AppendLine("Date\t\tAmount\tBalance\tNote"); + foreach (var item in allTransactions) + { + balance += item.Amount; + report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}"); + } + + return report.ToString(); + } + + // Added for OO tutorial: + + // + public virtual void PerformMonthEndTransactions() { } + // + } +} diff --git a/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/GiftCardAccount.cs b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/GiftCardAccount.cs new file mode 100644 index 0000000000000..4bb6de539649a --- /dev/null +++ b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/GiftCardAccount.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OOProgramming +{ + public class GiftCardAccount : BankAccount + { + // + private decimal _monthlyDeposit = 0m; + + public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance) + => _monthlyDeposit = monthlyDeposit; + // + + // + public override void PerformMonthEndTransactions() + { + if (_monthlyDeposit != 0) + { + MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit"); + } + } + // + } +} diff --git a/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/InterestEarningAccount.cs b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/InterestEarningAccount.cs new file mode 100644 index 0000000000000..fbfec63ef85ff --- /dev/null +++ b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/InterestEarningAccount.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OOProgramming +{ + public class InterestEarningAccount : BankAccount + { + // + public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance) + { + } + // + + // + public override void PerformMonthEndTransactions() + { + if (Balance > 500m) + { + var interest = Balance * 0.05m; + MakeDeposit(interest, DateTime.Now, "apply monthly interest"); + } + } + // + } +} diff --git a/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/LineOfCreditAccount.cs b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/LineOfCreditAccount.cs new file mode 100644 index 0000000000000..c0d8c7a793c69 --- /dev/null +++ b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/LineOfCreditAccount.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OOProgramming +{ + class LineOfCreditAccount : BankAccount + { + // + public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name, initialBalance, -creditLimit) + { + } + // + + // + public override void PerformMonthEndTransactions() + { + if (Balance < 0) + { + // Negate the balance to get a positive interest charge: + var interest = -Balance * 0.07m; + MakeWithdrawal(interest, DateTime.Now, "Charge monthly interest"); + } + } + // + + // + protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) => + isOverdrawn + ? new Transaction(-20, DateTime.Now, "Apply overdraft fee") + : default; + // + } +} diff --git a/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/Program.cs b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/Program.cs new file mode 100644 index 0000000000000..7fc69baf8e516 --- /dev/null +++ b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/Program.cs @@ -0,0 +1,75 @@ +using System; + +namespace OOProgramming +{ + class Program + { + static void Main(string[] args) + { + IntroToClasses(); + + // + var giftCard = new GiftCardAccount("gift card", 100, 50); + giftCard.MakeWithdrawal(20, DateTime.Now, "get expensive coffee"); + giftCard.MakeWithdrawal(50, DateTime.Now, "buy groceries"); + giftCard.PerformMonthEndTransactions(); + // can make additional deposits: + giftCard.MakeDeposit(27.50m, DateTime.Now, "add some additional spending money"); + Console.WriteLine(giftCard.GetAccountHistory()); + + var savings = new InterestEarningAccount("savings account", 10000); + savings.MakeDeposit(750, DateTime.Now, "save some money"); + savings.MakeDeposit(1250, DateTime.Now, "Add more savings"); + savings.MakeWithdrawal(250, DateTime.Now, "Needed to pay monthly bills"); + savings.PerformMonthEndTransactions(); + Console.WriteLine(savings.GetAccountHistory()); + // + + // + var lineOfCredit = new LineOfCreditAccount("line of credit", 0, 2000); + // How much is too much to borrow? + lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly advance"); + lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount"); + lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for repairs"); + lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on repairs"); + lineOfCredit.PerformMonthEndTransactions(); + Console.WriteLine(lineOfCredit.GetAccountHistory()); + // + } + + private static void IntroToClasses() + { + var account = new BankAccount("", 1000); + Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} balance."); + + account.MakeWithdrawal(500, DateTime.Now, "Rent payment"); + Console.WriteLine(account.Balance); + account.MakeDeposit(100, DateTime.Now, "friend paid me back"); + Console.WriteLine(account.Balance); + + Console.WriteLine(account.GetAccountHistory()); + + // Test that the initial balances must be positive: + try + { + var invalidAccount = new BankAccount("invalid", -55); + } + catch (ArgumentOutOfRangeException e) + { + Console.WriteLine("Exception caught creating account with negative balance"); + Console.WriteLine(e.ToString()); + } + + // Test for a negative balance + try + { + account.MakeWithdrawal(750, DateTime.Now, "Attempt to overdraw"); + } + catch (InvalidOperationException e) + { + Console.WriteLine("Exception caught trying to overdraw"); + Console.WriteLine(e.ToString()); + } + } + } +} diff --git a/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/object-oriented-programming.csproj b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/object-oriented-programming.csproj new file mode 100644 index 0000000000000..0bcab20846acb --- /dev/null +++ b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/object-oriented-programming.csproj @@ -0,0 +1,11 @@ + + + + Exe + netcoreapp3.1 + OOProgramming + oo-programming + enable + + + diff --git a/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/transaction.cs b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/transaction.cs new file mode 100644 index 0000000000000..945a8ed1373bf --- /dev/null +++ b/docs/csharp/tutorials/intro-to-csharp/snippets/object-oriented-programming/transaction.cs @@ -0,0 +1,18 @@ +using System; + +namespace OOProgramming +{ + public class Transaction + { + public decimal Amount { get; } + public DateTime Date { get; } + public string Notes { get; } + + public Transaction(decimal amount, DateTime date, string note) + { + this.Amount = amount; + this.Date = date; + this.Notes = note; + } + } +} diff --git a/samples/snippets/csharp/classes-quickstart/README.md b/samples/snippets/csharp/classes-quickstart/README.md deleted file mode 100644 index 49dfadf9af6af..0000000000000 --- a/samples/snippets/csharp/classes-quickstart/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# C# Classes and Objects Sample - -This sample is part of the [Classes and Objects tutorial](https://docs.microsoft.com/dotnet/csharp/tutorials/intro-to-csharp/introduction-to-classes) -for learning C# features. Please see that topic for detailed steps on the code -for this sample. - -## Key Features - -This sample demonstrates introduction of object oriented programming with classes and objects in C#, which explains about creation and usage of classes, objects, constructors, properties and methods. - -## Build and Run - -To build and run the sample, type the following two commands: - -```dotnetcli -dotnet restore -dotnet run -``` - -`dotnet restore` restores the dependencies for this sample. - -`dotnet run` builds the sample and runs the output assembly. - -**Note:** Starting with .NET Core 2.0 SDK, you don't have to run [`dotnet restore`](https://docs.microsoft.com/dotnet/core/tools/dotnet-restore) because it's run implicitly by all commands that require a restore to occur, such as `dotnet new`, `dotnet build` and `dotnet run`. It's still a valid command in certain scenarios where doing an explicit restore makes sense, such as [continuous integration builds in Azure DevOps Services](https://docs.microsoft.com/azure/devops/build-release/apps/aspnet/build-aspnet-core) or in build systems that need to explicitly control the time at which the restore occurs.