├── .gitignore ├── 001-Getting-Started └── README.md ├── 002-Hello-CSharp ├── Ex01 │ ├── Program.cs │ └── project.json ├── Ex02 │ ├── Program.cs │ └── project.json ├── Ex03 │ ├── Program.cs │ └── project.json └── README.md ├── 003-Language-Basics ├── Ex01 │ ├── Program.cs │ └── project.json ├── Ex02 │ ├── Program.cs │ └── project.json ├── Ex03 │ ├── Program.cs │ └── project.json └── README.md ├── 004-Properties ├── Ex01 │ ├── Person.cs │ ├── Program.cs │ └── project.json ├── Ex02 │ ├── Person.cs │ ├── Program.cs │ └── project.json ├── Ex03 │ ├── Person.cs │ ├── Program.cs │ └── project.json ├── Ex04 │ ├── Person.cs │ ├── Program.cs │ └── project.json └── README.md ├── 005-Lambdas ├── Ex01 │ ├── Program.cs │ └── project.json ├── Ex02 │ ├── Program.cs │ └── project.json ├── Ex03 │ ├── Person.cs │ ├── Program.cs │ └── project.json └── README.md ├── 006-Linq ├── Ex01 │ ├── Program.cs │ └── project.json ├── Ex02 │ ├── Program.cs │ └── project.json ├── Ex03 │ ├── Person.cs │ ├── Program.cs │ └── project.json ├── Ex04 │ ├── Person.cs │ ├── Program.cs │ └── project.json └── README.md ├── 007-Async ├── Ex01 │ ├── DoSomething.cs │ ├── Program.cs │ └── project.json ├── Ex02 │ ├── DoSomething.cs │ ├── Program.cs │ └── project.json ├── Ex03 │ ├── DoSomething.cs │ ├── Program.cs │ └── project.json └── README.md ├── 008-Asp.net └── README.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | 238 | #VSCode 239 | .vscode/ 240 | -------------------------------------------------------------------------------- /001-Getting-Started/README.md: -------------------------------------------------------------------------------- 1 | #Getting Started with C# on Linux 2 | This file is part of a multi-series workshop on learning C# on Linux available [here](../README.md). 3 | 4 | Author: [Martin Woodward](https://github.com/MartinWoodward) 5 | 6 | First of all we want to get .NET Core installed on your environment. 7 | For instructions see http://dot.net/core and follow the instructions for 8 | the platform of your choice. 9 | 10 | ## Getting started on Fedora 25 11 | 12 | At the time of writing, the RPM packages for .NET Core are not publicly available on Fedora 25 (they exist 13 | for Fedora 23 & 24). Therefore we must install from source - see the 14 | [instructions on GitHub](https://github.com/dotnet/core/pull/326#issuecomment-262479168) to learn more. 15 | 16 | If you would like to install the very latest (Daily Build) of .NET core then take a look at the [dotnet/core-setup](https://github.com/dotnet/core-setup/blob/master/README.md) repo. 17 | 18 | Alternativelu 19 | ``` 20 | sudo dnf copr enable nmilosev/dotnet-clean 21 | sudo dnf install dotnetcore -y 22 | ``` 23 | 24 | ## Conclusion 25 | That's it - before moving on from this point you should have dotnet installed on your system. You should be able to 26 | type `dotnet --version` to verify. Next, lets create a new application 27 | and learn about the structure of a C# program. 28 | 29 | --- 30 | - Next: [Tutorial 2 - Hello C# World](../002-Hello-CSharp/) 31 | - Back to [Table of Contents](../README.md) 32 | -------------------------------------------------------------------------------- /002-Hello-CSharp/Ex01/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | Console.WriteLine("Hello World!"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /002-Hello-CSharp/Ex01/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /002-Hello-CSharp/Ex02/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | Console.WriteLine("Hello Linux World!"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /002-Hello-CSharp/Ex02/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /002-Hello-CSharp/Ex03/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Program 6 | { 7 | public static int Main(string[] args) 8 | { 9 | Console.WriteLine("Hello Linux World!"); 10 | return 1; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /002-Hello-CSharp/Ex03/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /002-Hello-CSharp/README.md: -------------------------------------------------------------------------------- 1 | # Hello C# World 2 | This file is part of a multi-series workshop on learning C# on Linux available [here](../README.md). 3 | 4 | Author: [Martin Woodward](https://github.com/MartinWoodward) 5 | 6 | Before you start you want to make sure that you have .NET Core installed and configured in your path 7 | ``` 8 | dotnet --version 9 | ``` 10 | Assuming the command above works, you are good to go! If not, please see 11 | [Tutorial 1 - Getting Started with C# on Linux](../001-Getting-Started/). 12 | 13 | ## My First C# Program 14 | 15 | Now you have [.NET Core installed](../001-Getting-Started/), let's create our first C# program. 16 | To begin we need to create a folder to store our C# code project in, i.e. 17 | ``` 18 | mkdir -p ~/source/dotnet/002-hello 19 | cd ~/source/dotnet/002-hello 20 | ``` 21 | Then we want to create a new C# project in it 22 | ``` 23 | dotnet new 24 | ``` 25 | This will create the following files in that directory 26 | - *Program.cs* - the actual program, with a basic Hello World sample ready to run 27 | - *project.json* - a meta-data file that describes the entire .NET project to the compiler 28 | and other tooling. 29 | 30 | ## Getting ready to run 31 | Now we have an application, the next thing we have to do it tell NuGet to download the dependencies 32 | we need for our project. Do this by typing 33 | ``` 34 | dotnet restore 35 | ``` 36 | 37 | ## Compiling and running your project 38 | Then to build and run your application, simply type 39 | ``` 40 | dotnet run 41 | ``` 42 | That's all there is to it. Now let's take a look at `Program.cs` and make some changes. 43 | 44 | ## The Structure of a C# program 45 | Open `Program.cs` in your favorite editor and take a look at the contents. After you ran 46 | `dotnet new` the following file would have been created 47 | ```c# 48 | using System; 49 | 50 | namespace ConsoleApplication 51 | { 52 | public class Program 53 | { 54 | public static void Main(string[] args) 55 | { 56 | Console.WriteLine("Hello World!"); 57 | } 58 | } 59 | } 60 | ``` 61 | Note that C# ignores whitespace so while the code above is indented for readability it is not required. 62 | Lines of code are terminated with a semi-colon (`;`) and blocks of code are grouped using curly braces (`{ }`). 63 | The .NET Core codebase itself uses use [Allman style](http://en.wikipedia.org/wiki/Indent_style#Allman_style) 64 | braces, where each brace begins on a new line. A single line statement block can go without braces but 65 | many developers enjoy debating if that [is evil](https://www.imperialviolet.org/2014/02/22/applebug.html) 66 | or not. (It is) 67 | 68 | ### Using Directive 69 | The first line you see is the `using` directive. `using` is similar to `import` in other languages like Java, Python or Go. 70 | It tells the C# compiler where to find 71 | the classes that you want to call. In the example above, the full name of `Console` is actually 72 | `System.Console` but the `using System` directive means that the compiler automatically looks 73 | for the `Console` class with-in the `System` namespace. 74 | 75 | ### Namespaces 76 | A `namespace` is a way of grouping classes together in a way that limits their scope and avoids name 77 | conflicts. This is similar to `package` in Java however in C# while it is convention to have a hirerarchy 78 | of namespaces represented as a tree structure in a directory, you don't have to. A single `.cs` file 79 | could declare multiple classes in multiple namespaces - though that would be a little bit evil. In the 80 | example code created, you are defining `Program` to live in the `ConsoleApplication` namespace, therefore 81 | the full name of the class would be `ConsoleApplication.Program`. 82 | 83 | In .NET, code is organized into `assemblies` - a package of one or more namespaces of code in a shared library (a DLL) or an executable. Shared libraries (including the .NET Framework itself) are typically grouped into NuGet packages for distribution. 84 | 85 | ### Main() 86 | A C# program must contain a `public static` method called `Main` which controls the start and end of 87 | the process and there can only be one `Main` method per project. When the program is executed 88 | the .NET runtime will look for the class containing the 'Main' method and execute it. You can 89 | change the class name and namespace in your example and the `Main` method will still execute when 90 | you run the application. 91 | 92 | You can declare the Main method in two ways: 93 | ```c# 94 | public static void Main(string[] args) 95 | { 96 | // Do Something 97 | } 98 | ``` 99 | Which would always return a `0` exit code, or if you want to have control of the exit codes 100 | returned by your application you can declare your exit code explicitly by indicating that your 101 | `Main` method returns an `int` and then returning an integer in your `Main` method. 102 | ```c# 103 | public static int Main(string[] args) 104 | { 105 | // Do Something 106 | return 0; 107 | } 108 | ``` 109 | You can run the code above and verify the exit code returned by typing 110 | ``` 111 | dotnet run 112 | echo $? 113 | ``` 114 | 115 | ## Comments 116 | Note in the example above, we are showing comments. In common with many languages 117 | C# supports C style comments, i.e 118 | ```c# 119 | int x = 1; // Single line comment 120 | /* You can always 121 | * spread you comments out over many lines 122 | */ 123 | ``` 124 | 125 | ## Visibility 126 | In the example above, the `Main` method was declared with a visibility of `public` so that is can be accessed 127 | by code in the same assembly of any assembly that references it. The other most common access modifier to 128 | control visibility is 'private' which means the code can be accessed by the same class or struct. 'protected' 129 | means that the code can be accessed by the class or anything that derrives from that class and 'internal' code 130 | can be accessed by any code in that same assembly but not from another assembly. Note that in C# the access 131 | modifier visibility is scoped at the assembly level not at the namespace level. For more information on all 132 | the access modifies in C# please see the 133 | [reference documentation](https://msdn.microsoft.com/en-us/library/ms173121.aspx). 134 | 135 | ## Exercises 136 | 137 | 1. Create a new .NET Project and run it. 138 | 2. Modify the program to change the message it returns and run it. 139 | 3. Modify the program to return an exit code of 1. 140 | 141 | ## Conclusion 142 | In this tutorial we learned how to create a new C# application, the structure of a basic C# 143 | program, how to run the application and how to change the exit code of the process. 144 | 145 | ## Additional Information 146 | - [Using directive in C#](https://msdn.microsoft.com/en-us/library/sf0df423.aspx) 147 | - [Namespaces](https://msdn.microsoft.com/en-us/library/zz9ayh33.aspx) 148 | - [System.Console](https://msdn.microsoft.com/en-us/library/system.console.aspx) 149 | - [Main() and Command-Line Arguments](https://msdn.microsoft.com/en-us/library/acy3edy3.aspx) 150 | - [Visibility and Access Modifies in C#](https://msdn.microsoft.com/en-us/library/ms173121.aspx) 151 | - [NuGet](https://docs.nuget.org/) 152 | - [project.lock.json](https://github.com/aspnet/Home/wiki/Lock-file) 153 | 154 | --- 155 | - Next: [Tutorial 3 - C# Language Basics](../003-Language-Basics/) 156 | - Previous: [Tutorial 1 - Getting Started with C# on Linux](../001-Getting-Started/) 157 | - Back to [Table of Contents](../README.md) 158 | 159 | -------------------------------------------------------------------------------- /003-Language-Basics/Ex01/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | // Define a default name 10 | string name = "World"; 11 | if (args != null && args.Length > 0) 12 | { 13 | // If we've passed any command line arguments, grab the first as the name 14 | name = args[0]; 15 | } 16 | // Use a format string to print the name in the message 17 | Console.WriteLine("Hello {0}!", name); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /003-Language-Basics/Ex01/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /003-Language-Basics/Ex02/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | foreach (var name in args) 10 | { 11 | Console.WriteLine("Hello {0}!", name); 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /003-Language-Basics/Ex02/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /003-Language-Basics/Ex03/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | int numNames = 0; 10 | int totalChars = 0; 11 | foreach (var name in args) 12 | { 13 | numNames++; 14 | totalChars += name.Length; 15 | Console.WriteLine("Hello {0}!", name); 16 | } 17 | Console.WriteLine("Total number of names: {0} ({1} characters)", numNames, totalChars); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /003-Language-Basics/Ex03/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /003-Language-Basics/README.md: -------------------------------------------------------------------------------- 1 | # C# Language Basics 2 | This file is part of a multi-series workshop on learning C# on Linux available [here](../README.md). 3 | 4 | Author: [Martin Woodward](https://github.com/MartinWoodward) 5 | 6 | In this tutorial we will quickly cover the basics of the C# language. Future tutorials will dig deeper into 7 | features such as Language Integrated Query (LINQ) and Asynchronous programming. 8 | 9 | ## Introduction 10 | 11 | C# is a C++ style language and therefore the basic syntax will be familiar to any developer who understands C, C++ 12 | Java or related languages. It is a managed language designed to run in a Common Language Runtime benefiting from 13 | garbage collection and just-it-time compilation. C# has a simple syntax that is powerful in nature and is designed 14 | to reduce the amount of boiler-plate type code that a developer needs to create for an application. But it also 15 | provides simple mechanisms for interoperability with and library that provides a C style API including the ability 16 | to easily invoke native code and have direct access to memory through use of C++ style pointers. 17 | 18 | ## Variables 19 | 20 | Variables can be declared with a scope that is local or instance (class). C# is a statically typed language so each 21 | variable has a specific type but the compiler also supports the `var` keyword to mean that the type is inferred by 22 | the compiler at compilation time. Therefore the following are both valid and compile to the same thing. 23 | ```c# 24 | int x = 1; 25 | var y = 1; 26 | 27 | // Therefore 28 | int z = x + y; 29 | // Would return an int with a value of 2. 30 | ``` 31 | You can also declare variables with-in the scope of the instance of the class or define constants at the class level 32 | as shown below. 33 | ```c# 34 | public class Program 35 | { 36 | private static readonly string _message = "Hello World!"; 37 | public static void Main(string[] args) 38 | { 39 | Console.WriteLine(_message); 40 | } 41 | } 42 | ``` 43 | Note that C# has a `const` keyword that declares a constant at compilation time, however in general it is best 44 | practice to instead make use of the `static readonly` keywords to indicate to the compiler that a variable is a 45 | constant at runtime and does not change once created. 46 | 47 | Variable and method naming is always fun. The convention is to use camalCased for private and PascalCased for class 48 | names, public methods and properties. The C# coding styles used by the .NET Core team themselves are 49 | [available on GitHub](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md). 50 | 51 | ## Types 52 | As mentioned previously, C# is statically typed. In the examples previously `int` and `string` are simply aliases to 53 | the underlying implementation of them in the Base Class Library (BCL). You can find the source code for these 54 | implementations on GitHub. 55 | - Source for [System.Int32](https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Int32.cs) 56 | - Source for [System.String](https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/String.cs) 57 | 58 | You can create your own types by defining a class for them and providing your implementation. For example 59 | ```c# 60 | public class Food 61 | { 62 | private string foodStuff; 63 | private int howNice; 64 | 65 | public Food(string foodStuff, int howNice) 66 | { 67 | this.foodStuff = foodStuff; 68 | this.howNice = howNice; 69 | } 70 | } 71 | ``` 72 | And then you can create a new instance of your type in your application in either way as follows 73 | ```c# 74 | var chocolate = new Food("Chocolate", 99); 75 | Food brocoli = new Food("Brocoli", 1); 76 | ``` 77 | 78 | ## Inheritance 79 | C# is an Object Orientated language with type-safety and therefore provides strong controls around inheritance. 80 | By default, methods on a base class are not overridable unless they are declared as `virtual` meaning they 81 | may be overidden by a derived class or `abstract` which means they must be implemented in a derived class. You 82 | can define a class as `abstract` if you do not wish it to be directly instantiated with the `new` keyword and must 83 | have an implementation to be created. A class can prevent other classes from inheriting it by declaring itself or 84 | a member as `sealed`. C# deliberately only supports single inheritance of classes so the way that 85 | a class implements multiple behaviours is by the use of interfaces. 86 | 87 | In the example below we bring back our `Food` class but add a method of `Eat` and mark it as `virtual`. Then we 88 | derrive two types of `Food`, `Chocolate` and '`Brocoli`. We then overide the `Eat` method of `Brocoli` to throw 89 | an exception. 90 | ```c# 91 | public class Food 92 | { 93 | private string foodStuff; 94 | private int howNice; 95 | 96 | public Food(string foodStuff, int howNice) 97 | { 98 | this.foodStuff = foodStuff; 99 | this.howNice = howNice; 100 | } 101 | 102 | public virtual void Eat() 103 | { 104 | Console.WriteLine("nom nom"); 105 | } 106 | } 107 | 108 | public class Chocolate : Food 109 | { 110 | public Chocolate() : base("Chocolate", 99) 111 | { } 112 | } 113 | 114 | public class Brocoli : Food 115 | { 116 | public Brocoli() : base("Brocoli", 1) 117 | { } 118 | 119 | public override void Eat() 120 | { 121 | throw new InvalidOperationException("Sorry, but eating Brocoli is not allowed"); 122 | } 123 | } 124 | ``` 125 | 126 | > **Note** 127 | > 128 | > A colon (`:`) is used to indicate a class inherits from another class or interface. If providing a list of 129 | > multiple interfaces then they are comma (`,`) separated. A `:` is also used by a constructor in a derived class 130 | > to call a constructor in the base class (instead of something of using the `this` keyword like you would in a language 131 | > like Java). 132 | > 133 | > Also note that an Exception was thrown without it being explicitly declared. In c#, exceptions occur at runtime 134 | > but may be caught using a `try`,`catch`,`finally` block. 135 | 136 | For more information see the following sections of the C# Programming Guide 137 | - [Inheritance](https://msdn.microsoft.com/en-us/library/ms173149.aspx) 138 | - [Polymorphism](https://msdn.microsoft.com/en-us/library/ms173152.aspx) 139 | - [Abstract and Sealed Classes and Class Members](https://msdn.microsoft.com/en-us/library/ms173150.aspx) 140 | - [Interfaces](https://msdn.microsoft.com/en-us/library/ms173156.aspx) 141 | - [Exceptions and Exception Handling](https://msdn.microsoft.com/en-us/library/ms173160.aspx) 142 | 143 | 144 | ## Operators 145 | You can use all the operators you would expect for example 146 | ```c# 147 | int x = (1 + 5 - 2) / (2 * 2); // x = 1 148 | x++; // Same as saying x = x + 1 (which would now make x=2) 149 | x--; // Same as saying x = x - 1 (which would make x=1) 150 | Console.WriteLine(++x); // Would first increment x to 2 then display the result 151 | Console.WriteLine(--x); // Would first decrement x to 1 then display the result 152 | int i = 42 153 | Console.WriteLine(i == 42); // Would display 'True' 154 | ``` 155 | The full range of operators in C# is available in the 156 | [reference documentation](https://msdn.microsoft.com/en-us/library/6a71f45d.aspx) 157 | 158 | > **Note:** Assignment is performed using a single `=` where-as the double `==` is the test for equality. 159 | 160 | You can [implement operators in your own types](https://msdn.microsoft.com/en-us/library/8edha89s.aspx), or make use of some operators with common system types. 161 | For example to append strings you can do 162 | ```c# 163 | String message = "Hello " + "World!"; 164 | Console.WriteLine("Hello World!" == message); // Would display 'True' 165 | ``` 166 | (To see the overload for the equality operator `==` in `System.String` take 167 | a look at the [source on GitHub](https://github.com/dotnet/coreclr/blob/21f8416ad4204afc18ce315d99baa5f4ada28d9a/src/mscorlib/src/System/String.cs#L731-L733)) 168 | 169 | The C# compiler is also able to auto-cast types for you, therefore the following is valid syntax as the compiler will 170 | first convert the integer 42 into it's string representation, `"42"` and then concatenate it to the previous string. 171 | ```c# 172 | Console.WriteLine("The answer is :" + 42); 173 | ``` 174 | 175 | ## Collections 176 | .NET has a rich set of collections out the box. As .NET has also provides a rich implementation of generics for a very 177 | long time, all of the collections have generic versions that are commonly used. Using them is easy 178 | ```c# 179 | // Create a list that must contain objects of type String 180 | List listOfStrings = new List { "First", "Second", "Third" }; 181 | 182 | // listOfStrings can accept only strings, both on read and write. 183 | listOfStrings.Add("Fourth"); 184 | 185 | // Below will throw a compile-time error, since the type parameter 186 | // specifies this list as containing only strings. 187 | listOfStrings.Add(1); 188 | ``` 189 | You can read more about Generics in the [reference documentation](http://dotnet.github.io/docs/concepts/generics.html) 190 | where you can also find a list of [common generic collections](http://dotnet.github.io/api/System.Collections.Generic.html). 191 | 192 | ## String Formatting 193 | The String class in C# supports the formatting of strings where a format can be passed along with number of parameters 194 | to convert into strings and insert into the result. 195 | ```c# 196 | String primes = String.Format("Prime numbers {0} than {1} are: {2}, {3}, {4}, {5}, {6}", 197 | "less", "ten", 1, 2, 3, 5, 7 ); 198 | Console.WriteLine(primes); 199 | ``` 200 | For convenience, many methods that output messages have an overload that accepts a format string and an array of 201 | objects. `Console.WriteLine` is one such example, therefore the code above could be simplified into 202 | ```c# 203 | Console.WriteLine("Prime numbers {0} than {1} are: {2}, {3}, {4}, {5}, {6}", 204 | "less", "ten", 1, 2, 3, 5, 7); 205 | ``` 206 | 207 | For more information see the reference documentation on [Composite String Formats](https://msdn.microsoft.com/en-us/library/txafckwd.aspx). 208 | 209 | ## Control Flow 210 | The `if` operator in C# works pretty much as you would expect, remembering that C# likes curly braces and ignores 211 | whitespace. 212 | ```c# 213 | // Change the values of these variables to test the results. 214 | bool Condition1 = true; 215 | bool Condition2 = true; 216 | bool Condition3 = true; 217 | bool Condition4 = true; 218 | 219 | if (Condition1) 220 | { 221 | // Condition1 is true. 222 | } 223 | else if (Condition2) 224 | { 225 | // Condition1 is false and Condition2 is true. 226 | } 227 | else if (Condition3) 228 | { 229 | if (Condition4) 230 | { 231 | // Condition1 and Condition2 are false. Condition3 and Condition4 are true. 232 | } 233 | else 234 | { 235 | // Condition1, Condition2, and Condition4 are false. Condition3 is true. 236 | } 237 | } 238 | else 239 | { 240 | // Condition1, Condition2, and Condition3 are false. 241 | } 242 | ``` 243 | The `switch` statement in C# is also pretty standard except that it can also use the equality operator. For example 244 | the following is a valid `switch` statement in C# 245 | ```c# 246 | Console.WriteLine("Coffee sizes: 1=small 2=medium 3=large"); 247 | Console.Write("Please enter your selection: "); 248 | string str = Console.ReadLine(); 249 | int cost = 0; 250 | 251 | // Notice the goto statements in cases 2 and 3. The base cost of 25 252 | // is added to the additional cost for the medium and large sizes. 253 | switch (str) 254 | { 255 | case "1": 256 | case "small": 257 | cost += 25; 258 | break; 259 | case "2": 260 | case "medium": 261 | cost += 25; 262 | goto case "1"; 263 | case "3": 264 | case "large": 265 | cost += 50; 266 | goto case "1"; 267 | default: 268 | Console.WriteLine("Invalid selection. Please select 1, 2, or 3."); 269 | break; 270 | } 271 | if (cost != 0) 272 | { 273 | Console.WriteLine("Please insert {0} {1}.", cost, "cents"); 274 | } 275 | Console.WriteLine("Thank you for your business."); 276 | 277 | ``` 278 | ## Loops 279 | In c# you have the usual `do` `while` and `for` loops 280 | ```c# 281 | int x = 0; 282 | do 283 | { 284 | Console.WriteLine(x++); 285 | } while (x < 5); 286 | 287 | int y = 1; 288 | while (n < 6) 289 | { 290 | Console.WriteLine("n = {0}", n++); 291 | } 292 | 293 | for (int i = 1; i <= 5; i++) 294 | { 295 | Console.WriteLine(i); 296 | } 297 | ``` 298 | c# also supports the `foreach` loop which is recommended when iterating through arrays or collections. 299 | ```c# 300 | int[] fib = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 }; 301 | foreach (int element in fib) 302 | { 303 | System.Console.WriteLine(element); 304 | } 305 | 306 | List listOfStrings = new List { "First", "Second", "Third" }; 307 | foreach (var element in listOfStrings) 308 | { 309 | System.Console.WriteLine(element); 310 | } 311 | 312 | ``` 313 | Any class that implements the [`IEnumerable`](https://msdn.microsoft.com/en-us/library/system.collections.ienumerable.aspx) 314 | or [`IEnumerable`](https://msdn.microsoft.com/en-us/library/9eekhta0.aspx) interface can be iterated on using 315 | a `foreach` loop. `IEnumerable` is frequently used when passing around collections of data types and performing operations on them 316 | as we will see later. 317 | 318 | > **Note:** See the .NET Core documentation for [a deep-dive on Iterators and `foreach`](https://dotnet.github.io/docs/languages/csharp/iterators.html) 319 | 320 | ## Exercises 321 | 1. Create a HelloWorld application in .NET and allow a user to pass in a name to say hello to when running the 322 | application by typing `dotnet run Alice` 323 | 324 | 2. Modify your HelloWorld application to say hello to multiple names when passed in by typing `dotnet run Alice Bob`. 325 | 326 | 3. Display a count of the number of names that you have displayed and the sum total of characters in the names. 327 | 328 | ## Conclusion 329 | In this tutorial we learnt the basics of the C# syntax, how to declare variables and classes and perform operations 330 | on them. In the following tutorial we will learn more about properties in C#. 331 | 332 | ## Additional Information 333 | - [.NET Code Coding Style Guide](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md) 334 | - [C# Operators](https://msdn.microsoft.com/en-us/library/6a71f45d.aspx) 335 | - [Overloading Operators](https://msdn.microsoft.com/en-us/library/8edha89s.aspx) 336 | - [Inheritance](https://msdn.microsoft.com/en-us/library/ms173149.aspx) 337 | - [Polymorphism](https://msdn.microsoft.com/en-us/library/ms173152.aspx) 338 | - [Abstract and Sealed Classes and Class Members](https://msdn.microsoft.com/en-us/library/ms173150.aspx) 339 | - [Exceptions and Exception Handling](https://msdn.microsoft.com/en-us/library/ms173160.aspx) 340 | - [Interfaces](https://msdn.microsoft.com/en-us/library/ms173156.aspx) 341 | - [Composite String Formats](https://msdn.microsoft.com/en-us/library/txafckwd.aspx) 342 | - [if-else (C# Reference)](https://msdn.microsoft.com/en-us/library/5011f09h.aspx) 343 | - [Generics Overview](http://dotnet.github.io/docs/concepts/generics.html) 344 | - [Iterators and `foreach`](https://dotnet.github.io/docs/languages/csharp/iterators.html) 345 | 346 | --- 347 | - Next: [Tutorial 4 - Properties](../004-Properties/) 348 | - Previous: [Tutorial 2 - Hello C# World](../002-Hello-CSharp/) 349 | - Back to [Table of Contents](../README.md) 350 | 351 | -------------------------------------------------------------------------------- /004-Properties/Ex01/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Person 6 | { 7 | public Person(string firstName, string lastName, string email, DateTime dateOfBirth) 8 | { 9 | FirstName = firstName; 10 | LastName = lastName; 11 | Email = email; 12 | DateOfBirth = dateOfBirth; 13 | } 14 | 15 | public string FirstName{ get; set; } 16 | public string LastName { get; set; } 17 | public string Email { get; set; } 18 | public DateTime DateOfBirth { get; set; } 19 | public string FullName => $"{FirstName} {LastName}"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /004-Properties/Ex01/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | Console.WriteLine("Hello World!"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /004-Properties/Ex01/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /004-Properties/Ex02/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Person 6 | { 7 | public Person(string firstName, string lastName, string email, DateTime dateOfBirth) 8 | { 9 | FirstName = firstName; 10 | LastName = lastName; 11 | Email = email; 12 | DateOfBirth = dateOfBirth; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return FullName; 18 | } 19 | 20 | public string FirstName{ get; set; } 21 | public string LastName { get; set; } 22 | public string Email { get; set; } 23 | public DateTime DateOfBirth { get; set; } 24 | public string FullName => $"{FirstName} {LastName}"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /004-Properties/Ex02/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var people = new List 11 | { 12 | new Person("Alice", "Wonderland", "alice@contoso.com", new DateTime(1979, 11, 19)), 13 | new Person("Bob", "Mycroserft", "bob@contoso.com", new DateTime(1995, 03, 10)), 14 | new Person("Carol", "Reho", "carol@contoso.com", new DateTime(1986, 05, 26)) 15 | }; 16 | foreach(var person in people) 17 | { 18 | Console.WriteLine(person); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /004-Properties/Ex02/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /004-Properties/Ex03/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Person 6 | { 7 | public Person(string firstName, string lastName, string email, DateTime dateOfBirth) 8 | { 9 | FirstName = firstName; 10 | LastName = lastName; 11 | Email = email; 12 | DateOfBirth = dateOfBirth; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return $"{FullName}"; 18 | } 19 | 20 | public string FirstName{ get; set; } 21 | public string LastName { get; set; } 22 | public string Email { get; set; } 23 | public DateTime DateOfBirth { get; set; } 24 | public string FullName => $"{FirstName} {LastName}"; 25 | 26 | public int Age 27 | { 28 | get 29 | { 30 | return ( (Int32.Parse(DateTime.Today.ToString("yyyyMMdd")) - 31 | Int32.Parse(DateOfBirth.ToString("yyyyMMdd"))) / 10000); 32 | } 33 | } 34 | 35 | public bool Adult => Age >= 18; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /004-Properties/Ex03/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var people = new List 11 | { 12 | new Person("Alice", "Wonderland", "alice@contoso.com", new DateTime(1979, 11, 19)), 13 | new Person("Bob", "Mycroserft", "bob@contoso.com", new DateTime(1995, 03, 10)), 14 | new Person("Carol", "Reho", "carol@contoso.com", new DateTime(1986, 05, 26)) 15 | }; 16 | foreach(var person in people) 17 | { 18 | Console.WriteLine(person); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /004-Properties/Ex03/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /004-Properties/Ex04/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Person 6 | { 7 | public Person(string firstName, string lastName, string email, DateTime dateOfBirth) 8 | { 9 | FirstName = firstName; 10 | LastName = lastName; 11 | Email = email; 12 | DateOfBirth = dateOfBirth; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return $"{FullName} ({UserId})"; 18 | } 19 | 20 | public string FirstName{ get; set; } 21 | public string LastName { get; set; } 22 | public string Email { get; set; } 23 | public DateTime DateOfBirth { get; set; } 24 | public string FullName => $"{FirstName} {LastName}"; 25 | 26 | public int Age 27 | { 28 | get 29 | { 30 | return ( (Int32.Parse(DateTime.Today.ToString("yyyyMMdd")) - 31 | Int32.Parse(DateOfBirth.ToString("yyyyMMdd"))) / 10000); 32 | } 33 | } 34 | 35 | public bool Adult => Age >= 18; 36 | 37 | private string userId; 38 | public string UserId 39 | { 40 | get 41 | { 42 | if (String.IsNullOrWhiteSpace(userId)) 43 | { 44 | userId = (FirstName.Substring(0, 1) 45 | + LastName + DateOfBirth.ToString("yy")).ToLowerInvariant(); 46 | } 47 | return userId; 48 | } 49 | set 50 | { 51 | userId = value; 52 | } 53 | } 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /004-Properties/Ex04/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var people = new List 11 | { 12 | new Person("Alice", "Wonderland", "alice@contoso.com", new DateTime(1979, 11, 19)), 13 | new Person("Bob", "Mycroserft", "bob@contoso.com", new DateTime(1995, 03, 10)), 14 | new Person("Carol", "Reho", "carol@contoso.com", new DateTime(1986, 05, 26)) 15 | }; 16 | foreach(var person in people) 17 | { 18 | Console.WriteLine(person); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /004-Properties/Ex04/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /004-Properties/README.md: -------------------------------------------------------------------------------- 1 | # Properties 2 | 3 | This file is part of a multi-series workshop on learning C# on Linux available [here](../README.md). 4 | 5 | Author: [Bill Wagner](https://github.com/BillWagner) 6 | 7 | Properties are first class citizens in C#. The language 8 | defines syntax that enables developers to write code 9 | that accurately expresses their design intent. 10 | 11 | Properties behave like fields when they are accessed. 12 | However, unlike fields, properties are implemented 13 | with accessors that define the statements executed 14 | when a property is accessed or assigned. 15 | 16 | ## Property Syntax 17 | The syntax for properties is a natural extension to 18 | fields. A field defines a storage location: 19 | 20 | ```cs 21 | public class Person 22 | { 23 | public string FirstName; 24 | // remaining implementation removed from listing 25 | } 26 | ``` 27 | 28 | A property definition contains declarations for a `get` and 29 | `set` accessor that retrieves and assigns the value of that 30 | property: 31 | 32 | ```cs 33 | public class Person 34 | { 35 | public string FirstName 36 | { 37 | get; 38 | set; 39 | } 40 | // remaining implementation removed from listing 41 | } 42 | ``` 43 | 44 | The syntax shown above is the *auto property* syntax. The compiler 45 | generates the storage location for the field that backs up the 46 | property. The compiler also implements the body of the `get` and `set` accessors. 47 | You can also define the storage yourself, as shown below: 48 | 49 | ```cs 50 | public class Person 51 | { 52 | public string FirstName 53 | { 54 | get { return firstName; } 55 | set { firstName = value; } 56 | } 57 | private string firstName; 58 | // remaining implementation removed from listing 59 | } 60 | ``` 61 | 62 | The property definition shown above is a read-write property. Notice 63 | the keyword `value` in the set accessor. The `set` accessor always has 64 | a single parameter named `value`. The `get` accessor must return a value 65 | that is convertible to the type of the property (`string` in this example). 66 | 67 | That's the basics of the syntax. There are many different variations that support 68 | a variety of different design idioms. Let's explore those, and learn the syntax 69 | options for each. 70 | 71 | ## Scenarios 72 | 73 | The examples above showed one of the simplest cases of property definition: 74 | a read-write property with no validation. By writing the code you want in the 75 | `get` and `set` accessors, you can create many different scenarios. 76 | 77 | ### Validation 78 | 79 | You can write code in the `set` accessor to ensure that the values represented 80 | by a property are always valid. For example, suppose one rule for the `Person` 81 | class is that the name cannot be blank, or whitespace. You would write that as 82 | follows: 83 | 84 | ```cs 85 | public class Person 86 | { 87 | public string FirstName 88 | { 89 | get { return firstName; } 90 | set 91 | { 92 | if (string.IsNullOrWhiteSpace(value)) 93 | throw new ArgumentException("First name must not be blank"); 94 | firstName = value; 95 | } 96 | } 97 | private string firstName; 98 | // remaining implementation removed from listing 99 | } 100 | ``` 101 | 102 | The example above enforces the rule that the first name must not be blank, 103 | or whitespace. If a developer writes 104 | ```cs 105 | foo.FirstName = ""; 106 | ``` 107 | That assignment throws an `ArgumentException`. Because a property set accessor 108 | must have a void return type, you report errors in the set accessor by throwing an exception. 109 | 110 | That is a simple case of validation. You can extend this same syntax to anything needed 111 | in your scenario. You can check the relationships between different properties, or validate 112 | against any external conditions. Any valid C# statements are valid in a property accessor. 113 | 114 | ### Read-only 115 | 116 | Up to this point, all the property definitions you have seen are read/write properties 117 | with public accessors. That's not the only valid accessibility for properties. 118 | You can create read-only properties, or give different accessibility to the set and get 119 | accessors. Suppose that your `Person` class should only enable changing the value of the 120 | `FirstName` property from other methods in that class. You could give the set accessor 121 | `private` accessibility instead of `public`: 122 | 123 | ```cs 124 | public class Person 125 | { 126 | public string FirstName 127 | { 128 | get; 129 | private set; 130 | } 131 | // remaining implementation removed from listing 132 | } 133 | ``` 134 | 135 | Now, the `FirstName` property can be accessed from any code, but it can only be assigned 136 | from other code in the `Person` class. 137 | You can add any restrictive access modifier to either the set or get accessors. Any access modifier 138 | you place on the individual accessor must be more limited than the access modifier on the property 139 | definition. The above is legal because the `FirstName` property is `public`, but the set accessor is 140 | `private`. You could not declare a `private` property with a `public` accessor. Property declarations 141 | can also be declared `protected`, `internal`, `protected internal` or even `private`. 142 | 143 | It is also legal to place the more restrictive modifier on the `get` accessor. For example, you could 144 | have a `public` property, but restrict the `get` accessor to `private`. That scenario is rarely done 145 | in practice. 146 | 147 | ### Computed Properties 148 | 149 | A property does not need to simply return the value of a member field. You can create properties 150 | that return a computed value. Let's expand the `Person` object to return the full name, computed 151 | by concatenating the first and last names: 152 | 153 | ```cs 154 | public class Person 155 | { 156 | public string FirstName 157 | { 158 | get; 159 | set; 160 | } 161 | 162 | public string LastName 163 | { 164 | get; 165 | set; 166 | } 167 | 168 | public string FullName 169 | { 170 | get 171 | { 172 | return $"{FirstName} {LastName}"; 173 | } 174 | } 175 | } 176 | ``` 177 | 178 | The example above uses the *String Interpolation* syntax to create 179 | the formatted string for the full name. 180 | 181 | You can also use *Expression Bodied Members*, which provides a more 182 | succinct way to create the computed `FullName` property: 183 | 184 | ```cs 185 | public class Person 186 | { 187 | public string FirstName 188 | { 189 | get; 190 | set; 191 | } 192 | 193 | public string LastName 194 | { 195 | get; 196 | set; 197 | } 198 | 199 | public string FullName => $"{FirstName} {LastName}"; 200 | } 201 | ``` 202 | 203 | *Expression Bodied Members* use the *lambda expression* syntax to 204 | define a method that contain a single expression. Here, that 205 | expression returns the full name for the person object. 206 | 207 | ### Lazy Evaluated Properties 208 | 209 | You can mix the concept of a computed property with storage and create 210 | a *lazy evaluated property*. For example, you could update the `FullName` 211 | property so that the string formatting only happened the first time it 212 | was accessed: 213 | 214 | ```cs 215 | public class Person 216 | { 217 | public string FirstName 218 | { 219 | get; 220 | set; 221 | } 222 | 223 | public string LastName 224 | { 225 | get; 226 | set; 227 | } 228 | 229 | private string fullName; 230 | public string FullName 231 | { 232 | get 233 | { 234 | if (fullName == null) 235 | fullName = $"{FirstName} {LastName}"; 236 | return fullName; 237 | } 238 | } 239 | } 240 | ``` 241 | 242 | The above code contains a bug though. If code updates the value of 243 | either the `FirstName` or `LastName` property, the previously evaluated 244 | `fullName` field is invalid. You need to update the `set` accessors of the 245 | `FirstName` and `LastName` property so that the `fullName` field is calculated 246 | again: 247 | 248 | ```cs 249 | public class Person 250 | { 251 | private string firstName; 252 | public string FirstName 253 | { 254 | get { return firstName; } 255 | set 256 | { 257 | firstName = value; 258 | fullName = null; 259 | } 260 | } 261 | 262 | private string lastName; 263 | public string LastName 264 | { 265 | get { return lastName; } 266 | set 267 | { 268 | lastName = value; 269 | fullName = null; 270 | } 271 | } 272 | 273 | private string fullName; 274 | public string FullName 275 | { 276 | get 277 | { 278 | if (fullName == null) 279 | fullName = $"{FirstName} {LastName}"; 280 | return fullName; 281 | } 282 | } 283 | } 284 | ``` 285 | 286 | This final version evaluates the `FullName` property only when needed. 287 | If the previously calculated version is valid, it's used. If another 288 | state change invalidates the previously calculated version, it will be 289 | recalculated. Developers that use this class do not need to know the 290 | details of the implementation. None of these internal changes affect the 291 | use of the Person object. That's the key reason for using Properties to 292 | expose data members of an object. 293 | 294 | ### INotifyPropertyChanged 295 | 296 | A final scenario where you need to write code in a property accessor is to 297 | support the `INotifyPropertyChanged` interface used to notify data binding 298 | clients that a value has changed. When the value of a property changes, the object 299 | raises the `PropertyChanged` event 300 | to indicate the change. The data binding libraries, in turn, update display elements 301 | based on that change. The code below shows how you would implement `INotifyPropertyChanged` 302 | for the `FirstName` property of this person class. 303 | 304 | ```cs 305 | public class Person : INotifyPropertyChanged 306 | { 307 | public string FirstName 308 | { 309 | get { return firstName; } 310 | set 311 | { 312 | if (string.IsNullOrWhiteSpace(value)) 313 | throw new ArgumentException("First name must not be blank"); 314 | if (value != firstName) 315 | { 316 | PropertyChanged?.Invoke(this, 317 | new PropertyChangedEventArgs(nameof(FirstName))); 318 | } 319 | firstName = value; 320 | } 321 | } 322 | private string firstName; 323 | 324 | public event PropertyChangedEventHandler PropertyChanged; 325 | // remaining implementation removed from listing 326 | } 327 | ``` 328 | 329 | The `?.` operator is called 330 | the *null conditional operator*. It checks for a null reference before evaluating 331 | the right side of the operator. The end result is that if there are no subscribers 332 | to the `PropertyChanged` event, the code to raise the event doesn't execute. It would 333 | throw a `NullReferenceException` without this check in that case. This example also uses the new 334 | `nameof` operator to convert from the property name symbol to its text representation. 335 | Using `nameof` can reduce errors where you have mistyped the name of the property. 336 | 337 | Again, this is an example of a case where you can write code in your accessors to 338 | support the scenarios you need. 339 | 340 | ## Exercises 341 | 1. Create a class called `Person` with the following properties that must be defined 342 | - First name 343 | - Last name 344 | - Email address 345 | - Date of Birth 346 | 2. Store 3 people in a list and print out their details to the console 347 | 3. Add a read-only property that returns the following information 348 | - Age - their current age (in years) 349 | - Adult - a bool indicating if they are over 18 or not 350 | 4. Add a property called `UserID` which can be explicitly set but will default to a practical first time value 351 | if not defined 352 | 353 | ## Conclusion 354 | In this section we learnt about Properties in C# and the different ways to use them. We were also introduced to 355 | null conditional operators and learned how to implement the `INotifyPropertyChanged` interface. 356 | 357 | Properties are a form of smart fields in a class or object. From 358 | outside the object, they appear like fields in the object. However, 359 | properties can be implemented using the full palette of C# functionality. 360 | You can provide validation, different accessibility, lazy evaluation, 361 | or any requirements your scenarios need. 362 | 363 | ## Additional Information 364 | - [Lamda Expressions in C#](https://msdn.microsoft.com/en-gb/library/bb397687.aspx) 365 | - [Null Conditional Operators in C#](https://msdn.microsoft.com/en-us/library/dn986595.aspx) 366 | - [Events and Delegates](https://msdn.microsoft.com/en-gb/library/17sde2xt.aspx) 367 | - [`INotifyPropertyChanged` interface](https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx) 368 | - [`nameof` operator](https://msdn.microsoft.com/en-us/library/dn986596.aspx) 369 | 370 | --- 371 | - Next: [Tutorial 5 - Delegates and Lambda Expressions](../005-Lambdas/) 372 | - Previous: [Tutorial 1 - C# Language Basics](../003-Language-Basics/) 373 | - Back to [Table of Contents](../README.md) 374 | 375 | -------------------------------------------------------------------------------- /005-Lambdas/Ex01/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | List list = new List(); 11 | 12 | for (int i = 1; i <= 100; i++) 13 | { 14 | list.Add(i); 15 | } 16 | 17 | Console.WriteLine("Evens"); 18 | foreach (var item in list.FindAll(i => i % 2 == 0)) 19 | { 20 | Console.WriteLine(item); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /005-Lambdas/Ex01/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /005-Lambdas/Ex02/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | List list = new List(); 11 | 12 | for (int i = 1; i <= 100; i++) 13 | { 14 | list.Add(i); 15 | } 16 | 17 | Console.WriteLine("--- Evens ---"); 18 | foreach (var item in list.FindAll(i => i % 2 == 0)) 19 | { 20 | Console.WriteLine(item); 21 | } 22 | 23 | Console.WriteLine("--- Odds ---"); 24 | foreach (var item in list.FindAll(i => i % 2 != 0)) 25 | { 26 | Console.WriteLine(item); 27 | } 28 | 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /005-Lambdas/Ex02/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /005-Lambdas/Ex03/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Person 6 | { 7 | public Person(string firstName, string lastName, string email, DateTime dateOfBirth) 8 | { 9 | FirstName = firstName; 10 | LastName = lastName; 11 | Email = email; 12 | DateOfBirth = dateOfBirth; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return $"{FullName} ({UserId}) Age:{Age}"; 18 | } 19 | 20 | public string FirstName{ get; set; } 21 | public string LastName { get; set; } 22 | public string Email { get; set; } 23 | public DateTime DateOfBirth { get; set; } 24 | public string FullName => $"{FirstName} {LastName}"; 25 | 26 | public int Age 27 | { 28 | get 29 | { 30 | return ( (Int32.Parse(DateTime.Today.ToString("yyyyMMdd")) - 31 | Int32.Parse(DateOfBirth.ToString("yyyyMMdd"))) / 10000); 32 | } 33 | } 34 | 35 | public bool Adult => Age >= 18; 36 | 37 | private string userId; 38 | public string UserId 39 | { 40 | get 41 | { 42 | if (String.IsNullOrWhiteSpace(userId)) 43 | { 44 | userId = (FirstName.Substring(0, 1) 45 | + LastName + DateOfBirth.ToString("yy")).ToLowerInvariant(); 46 | } 47 | return userId; 48 | } 49 | set 50 | { 51 | userId = value; 52 | } 53 | } 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /005-Lambdas/Ex03/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var people = new List 11 | { 12 | new Person("Alice", "Wonderland", "alice@contoso.com", new DateTime(1979, 11, 19)), 13 | new Person("Bob", "Mycroserft", "bob@contoso.com", new DateTime(1995, 03, 10)), 14 | new Person("Carol", "Reho", "carol@contoso.com", new DateTime(1986, 05, 26)) 15 | }; 16 | foreach(var person in people) 17 | { 18 | Console.WriteLine(person); 19 | } 20 | 21 | Func, int> calculateAverageAge = 22 | (List list) => 23 | { int totalAges = 0; 24 | foreach(var p in list) 25 | { 26 | totalAges += p.Age; 27 | } 28 | int averageAge = totalAges / list.Count; 29 | return averageAge; 30 | }; 31 | 32 | Console.WriteLine("Average Age: {0}", calculateAverageAge(people)); 33 | 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /005-Lambdas/Ex03/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /005-Lambdas/README.md: -------------------------------------------------------------------------------- 1 | # Delegates and Lambda Expressions 2 | This file is part of a multi-series workshop on learning C# on Linux available [here](../README.md). 3 | 4 | Authors: [Ken Chen](https://github.com/chenkennt), [Martin Woodward](https://github.com/martinwoodward) 5 | 6 | Delegates are similar to function pointers in C++ function pointers but they are type safe. 7 | They are a disconnected way to pass a call to a method within the .NET Framework type system. 8 | 9 | Delegates are used in various APIs in the .NET world usually through lambda expressions, 10 | which in turn are a cornerstone of Language Integrated Query (LINQ). We will dig into LINQ in Tutorial 6. 11 | 12 | ## Delegates 13 | 14 | A `delegate` defines a type, which specifies a particular method signature. 15 | A method (static or instance) that satisfies this signature can be assigned to a variable of that delegate type, 16 | then can be called directly (with the appropriate arguments) or passed as an argument itself to another method 17 | and then called. The following example demonstrates the use of a delegate. 18 | 19 | ```cs 20 | public class Program 21 | { 22 | // Declate a delegate that takes a string and returns a string 23 | public delegate string Reverse(string s); 24 | 25 | // Statically define an implementation that matches the signaure of the delegate defined above 26 | public static string ReverseString(string s) 27 | { 28 | return new string(s.Reverse().ToArray()); 29 | } 30 | 31 | public static void Main(string[] args) 32 | { 33 | // Create an instance of the Reverse delegate implemented by the ReverseString method 34 | Reverse rev = ReverseString; 35 | 36 | // Invoke the delegate by passing in the appropriate parameters 37 | Console.WriteLine(rev("a string")); 38 | } 39 | } 40 | 41 | ``` 42 | 43 | 44 | In order to streamline the development process, .NET includes a set of generic delegate types that programmers can 45 | reuse and not have to create new types. These are `Func<>`, `Action<>` and `Predicate<>` 46 | 47 | - `Action<>` is used when there is a need to perform an action using the arguments of the delegate. 48 | - `Func<>` is used usually when you need to transform the arguments of the delegate into a different result. 49 | - `Predicate<>` is used when you need to determine if the argument satisfies the condition of the delegate. 50 | It could also be written as a `Func`. 51 | 52 | Therefore instead of a custom type we can now make use of the `Func<>` delegate 53 | 54 | ```cs 55 | public class Program 56 | { 57 | static string ReverseString(string s) 58 | { 59 | return new string(s.Reverse().ToArray()); 60 | } 61 | 62 | static void Main(string[] args) 63 | { 64 | Func rev = ReverseString; 65 | 66 | Console.WriteLine(rev("a string")); 67 | } 68 | } 69 | 70 | ``` 71 | 72 | ## Anonymous Delegates 73 | Having a method defined outside of the Main() method seems a bit superfluous. It is because of this that 74 | C# 2.0 introduced the concept of anonymous delegates. With their support you are able to create 75 | “inline” delegates without having to specify any additional type or method. This is similar to anonymous 76 | functions in Javascript or Go. You simply inline the definition of the delegate where you need it. 77 | 78 | For an example, lets use an anonymous delegate to filter out a list of only 79 | even numbers and then print them to the console. 80 | 81 | ```cs 82 | public class Program 83 | { 84 | public static void Main(string[] args) 85 | { 86 | List list = new List(); 87 | 88 | for (int i = 1; i <= 100; i++) 89 | { 90 | list.Add(i); 91 | } 92 | 93 | List result = list.FindAll( 94 | delegate(int no) // Declare our delegate and pass it to the FindAll 95 | { // method in the List collection to use to match 96 | return (no%2 == 0); // all items in the list meeting this criteria 97 | } // 98 | ); 99 | 100 | foreach (var item in result) 101 | { 102 | Console.WriteLine(item); 103 | } 104 | } 105 | } 106 | 107 | ``` 108 | However, even with this approach, there is still much code that we can throw away. This is where **lambda expressions** come into play. 109 | 110 | ## Lamda Expressions 111 | 112 | Lambda expressions, or just *lambdas* for short, were introduced first in C# 3.0. They are just a more convenient 113 | syntax for using delegates. They declare a signature and a method body, but don’t have an formal identity of 114 | their own, unless they are assigned to a delegate. Unlike delegates, they can be directly assigned as the 115 | left-hand side of event registration or in various LINQ clauses and methods. 116 | 117 | Since a lambda expression is just another way of specifying a delegate, we can rewrite the above sample as follows 118 | ```cs 119 | public class Program 120 | { 121 | 122 | public static void Main(string[] args) 123 | { 124 | List list = new List(); 125 | 126 | for (int i = 1; i <= 100; i++) 127 | { 128 | list.Add(i); 129 | } 130 | 131 | List result = list.FindAll(i => i % 2 == 0); // Our lamda, getting rid of all the boilerplate code 132 | 133 | foreach (var item in result) 134 | { 135 | Console.WriteLine(item); 136 | } 137 | } 138 | } 139 | 140 | ``` 141 | Let's break that syntax down some more. The delegate type is represented by the following lambda 142 | ```c# 143 | i => i % 2 == 0 144 | ``` 145 | To create a lambda you specify the input paramaters (if any) on the left hand side of the lambda operator `=>` 146 | and you put the statement block on the other side. So in this example we specify a parameter of `i` and return 147 | the value of `i % 2 == 0` which is our boolean test to indicate if `i` is even or not. 148 | 149 | If you take a look at the highlighted lines, you can see how a lambda expression looks like. 150 | Again, it is just a syntactical sugar for using the delegate type in .NET, so what happens under the 151 | covers is similar to what happens with the anonymous delegate. 152 | 153 | The example above shows the use of an expression lambda, the value of the right hand side is return. 154 | You can also use lambda to execute statements of code by ending each statement with a semi-colon (`;`) 155 | 156 | For example 157 | 158 | ```c# 159 | TestDelegate myDel = n => { string s = String.Format("Hello {0}!", n); Console.WriteLine(s); }; 160 | myDel("Alice"); // Writes out "Hello Alice!" 161 | myDel("Bob"); // Writes out "Hello Bob!" 162 | 163 | ``` 164 | 165 | ## Exercises 166 | 1. Write a console application that creates a `List` of all the numbers 1-100 and displays the even ones 167 | 168 | 2. Extend your application to display the even numbers then the odd numbers 169 | 170 | 3. Take your application from Tutorial 4 ([or the solution on GitHub](../004-Properties/Ex04/Program.cs)) and use a `Func` delegate 171 | to calculate the average age of the people in your list and display it on the console 172 | 173 | 174 | ## Additional Information 175 | - [Delegates](https://msdn.microsoft.com/en-us/library/ms173171.aspx) 176 | - [Anonymous Functions](https://msdn.microsoft.com/en-us/library/bb882516.aspx) 177 | - [Lambda expressions](https://msdn.microsoft.com/en-us/library/bb397687.aspx) 178 | 179 | --- 180 | - Next: [Tutorial 6 - Linq](../006-Linq/) 181 | - Previous: [Tutorial 4 - Properties](../004-Properties/) 182 | - Back to [Table of Contents](../README.md) 183 | 184 | -------------------------------------------------------------------------------- /006-Linq/Ex01/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | string[] strings = 11 | { 12 | "A penny saved is a penny earned.", 13 | "The early bird catches the worm.", 14 | "The pen is mightier than the sword." 15 | }; 16 | 17 | // Get the words containing an E 18 | var eWords = 19 | from sentence in strings 20 | let words = sentence.Split(' ') 21 | from word in words 22 | let w = word.ToLower() 23 | where w.IndexOf('e') >= 0 24 | select word; 25 | 26 | // Display them. 27 | foreach (var w in eWords) 28 | { 29 | Console.WriteLine($"\"{w}\""); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /006-Linq/Ex01/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /006-Linq/Ex02/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | string[] strings = 11 | { 12 | "A penny saved is a penny earned.", 13 | "The early bird catches the worm.", 14 | "The pen is mightier than the sword." 15 | }; 16 | 17 | // Get the words containing an E 18 | var eWords = 19 | from sentence in strings 20 | let words = sentence.Split(' ') 21 | from word in words 22 | let w = word.ToLower() 23 | where w.IndexOf('e') >= 0 24 | select word; 25 | 26 | // Sort by word length 27 | eWords = eWords.OrderBy(w => w.Length); 28 | 29 | // Display them. 30 | foreach (var w in eWords) 31 | { 32 | Console.WriteLine($"\"{w}\""); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /006-Linq/Ex02/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /006-Linq/Ex03/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Person 6 | { 7 | public Person(string firstName, string lastName, string email, DateTime dateOfBirth) 8 | { 9 | FirstName = firstName; 10 | LastName = lastName; 11 | Email = email; 12 | DateOfBirth = dateOfBirth; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return $"{FullName} ({UserId}) Age:{Age}"; 18 | } 19 | 20 | public string FirstName{ get; set; } 21 | public string LastName { get; set; } 22 | public string Email { get; set; } 23 | public DateTime DateOfBirth { get; set; } 24 | public string FullName => $"{FirstName} {LastName}"; 25 | 26 | public int Age 27 | { 28 | get 29 | { 30 | return ( (Int32.Parse(DateTime.Today.ToString("yyyyMMdd")) - 31 | Int32.Parse(DateOfBirth.ToString("yyyyMMdd"))) / 10000); 32 | } 33 | } 34 | 35 | public bool Adult => Age >= 18; 36 | 37 | private string userId; 38 | public string UserId 39 | { 40 | get 41 | { 42 | if (String.IsNullOrWhiteSpace(userId)) 43 | { 44 | userId = (FirstName.Substring(0, 1) 45 | + LastName + DateOfBirth.ToString("yy")).ToLowerInvariant(); 46 | } 47 | return userId; 48 | } 49 | set 50 | { 51 | userId = value; 52 | } 53 | } 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /006-Linq/Ex03/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ConsoleApplication 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | var people = new List 12 | { 13 | new Person("Alice", "Wonderland", "alice@contoso.com", new DateTime(1979, 11, 19)), 14 | new Person("Bob", "Mycroserft", "bob@contoso.com", new DateTime(1995, 03, 10)), 15 | new Person("Carol", "Reho", "carol@contoso.com", new DateTime(1986, 05, 26)) 16 | }; 17 | 18 | foreach(var person in people.OrderBy(p => p.Age)) 19 | { 20 | Console.WriteLine(person); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /006-Linq/Ex03/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /006-Linq/Ex04/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Person 6 | { 7 | public Person(string firstName, string lastName, string email, DateTime dateOfBirth) 8 | { 9 | FirstName = firstName; 10 | LastName = lastName; 11 | Email = email; 12 | DateOfBirth = dateOfBirth; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | return $"{FullName} ({UserId}) Age:{Age}"; 18 | } 19 | 20 | public string FirstName{ get; set; } 21 | public string LastName { get; set; } 22 | public string Email { get; set; } 23 | public DateTime DateOfBirth { get; set; } 24 | public string FullName => $"{FirstName} {LastName}"; 25 | 26 | public int Age 27 | { 28 | get 29 | { 30 | return ( (Int32.Parse(DateTime.Today.ToString("yyyyMMdd")) - 31 | Int32.Parse(DateOfBirth.ToString("yyyyMMdd"))) / 10000); 32 | } 33 | } 34 | 35 | public bool Adult => Age >= 18; 36 | 37 | private string userId; 38 | public string UserId 39 | { 40 | get 41 | { 42 | if (String.IsNullOrWhiteSpace(userId)) 43 | { 44 | userId = (FirstName.Substring(0, 1) 45 | + LastName + DateOfBirth.ToString("yy")).ToLowerInvariant(); 46 | } 47 | return userId; 48 | } 49 | set 50 | { 51 | userId = value; 52 | } 53 | } 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /006-Linq/Ex04/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ConsoleApplication 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | var people = new List 12 | { 13 | new Person("Alice", "Wonderland", "alice@contoso.com", new DateTime(1979, 11, 19)), 14 | new Person("Bob", "Mycroserft", "bob@contoso.com", new DateTime(1995, 03, 10)), 15 | new Person("Carol", "Reho", "carol@contoso.com", new DateTime(1986, 05, 26)) 16 | }; 17 | 18 | foreach(var person in people.OrderBy(p => p.Age)) 19 | { 20 | Console.WriteLine(person); 21 | } 22 | 23 | Console.WriteLine("Oldest: {0}", people.Max(p => p.Age)); 24 | 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /006-Linq/Ex04/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": {}, 8 | "frameworks": { 9 | "netcoreapp1.1": { 10 | "dependencies": { 11 | "Microsoft.NETCore.App": { 12 | "type": "platform", 13 | "version": "1.1.0" 14 | } 15 | }, 16 | "imports": "dnxcore50" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /006-Linq/README.md: -------------------------------------------------------------------------------- 1 | # Language Integrated Query (LINQ) 2 | 3 | This file is part of a multi-series workshop on learning C# on Linux available [here](../README.md). 4 | 5 | Authors: [Ken Chen](https://github.com/chenkennt), [Martin Woodward](https://github.com/martinwoodward) 6 | 7 | LINQ is a powerful set of features for .NET that allow you to write simple, declarative code for 8 | operating on data. LINQ provides language-level data querying capabilities and a 9 | [higher-order function](https://en.wikipedia.org/wiki/Higher-order_function) API in .NET as a way to write 10 | expressive, declarative code. The data being queries can be in many forms (such as in-memory objects, 11 | in a SQL database, or an XML document), but the LINQ code you write typically won’t look different regardless 12 | of the data source. 13 | 14 | The LINQ query syntax should look familiar to people familiar with traditional SQL queries: 15 | ```cs 16 | var linqExperts = from p in programmers 17 | where p.IsNewToLINQ 18 | select new LINQExpert(p); 19 | 20 | ``` 21 | 22 | Same example using the `IEnumerable` API syntax: 23 | 24 | ```cs 25 | var linqExperts = programmers.Where(p => IsNewToLINQ) 26 | .Select(p => new LINQExpert(p)); 27 | 28 | ``` 29 | 30 | ## LINQ is Expressive 31 | 32 | Imagine you have a list of pets, but want to convert it into a `Dictionary` 33 | where you can access a pet directly by its `RFID` value. Traditionally you 34 | could do this with a `foreach` 35 | 36 | ```cs 37 | var petLookup = new Dictionary(); 38 | 39 | foreach (var pet in pets) 40 | { 41 | petLookup.Add(pet.RFID, pet); 42 | } 43 | 44 | ``` 45 | The equivalent LINQ expression (using a [Lambda](../005-Landas) expression): 46 | 47 | ```cs 48 | var petLookup = pets.ToDictionary(pet => pet.RFID); 49 | 50 | ``` 51 | The code above is much more expressive as it reads exactly as per the intent (converting a `List` to a `Dictionary`) 52 | It's also a lot more terse. 53 | 54 | ## LINQ Providers Simplify Data Access 55 | 56 | For a significant chunk of software in the world, it revolves around dealing with data from some 57 | source (Databases, JSON, XML, etc). Often this involves learning a new API for each data source. 58 | LINQ simplifies this by abstracting common elements of data access into a query syntax which looks the same 59 | no matter which data source you pick. 60 | 61 | Consider the following example when finding all XML elements with a specific attribute value. 62 | 63 | ```cs 64 | public static FindAllElementsWithAttribute(XElement documentRoot, string elementName, 65 | string attributeName, string value) 66 | { 67 | return from el in documentRoot.Elements(elementName) 68 | where (string)el.Element(attributeName) == value 69 | select el; 70 | } 71 | 72 | ``` 73 | Writing code to manually traverse the XML document to perform this task would be far more challenging. 74 | 75 | Interacting with XML isn’t the only thing you can do with LINQ Providers. 76 | [Linq to SQL](https://msdn.microsoft.com/en-us/library/bb386976.aspx) is a fairly bare-bones 77 | Object-Relational Mapper (ORM) for an MSSQL Server Database. 78 | The [JSON.NET](http://www.newtonsoft.com/json/help/html/LINQtoJSON.htm) library provides efficient 79 | JSON Document traversal via LINQ. Furthermore, if there isn’t a library which does what you need, 80 | you can also [write your own LINQ Provider](https://msdn.microsoft.com/en-us/library/vstudio/Bb546158.aspx). 81 | 82 | ## Difference between the API and Query syntax 83 | 84 | As stated previously, there are two main ways to use Linq. The query syntax: 85 | ```cs 86 | var filteredItems = from item in myItems 87 | where item.Foo 88 | select item; 89 | 90 | ``` 91 | And the API/Method syntax that the compiler ultimately converts the query syntax to. 92 | 93 | ```cs 94 | var filteredItems = myItems.Where(item => item.Foo); 95 | 96 | ``` 97 | Query syntax and method syntax are semantically identical, but many people find query syntax simpler 98 | and easier to read. Some queries must be expressed as method calls. For example, you must use a method 99 | call to express a query that retrieves the number of elements that match a specified condition. 100 | You also must use a method call for a query that retrieves the element that has the maximum value 101 | in a source sequence. 102 | 103 | In addition, the query syntax allows for the use the `let` clause, which allows you to introduce and bind a 104 | variable within the scope of the expression and then using it in subsequent pieces of the expression. Reproducing 105 | the same code with only the API syntax can be done but the code generated can be more difficult to read. 106 | 107 | An example of use of the the `let` clause is as follows 108 | ```c# 109 | string[] strings = 110 | { 111 | "A penny saved is a penny earned.", 112 | "The early bird catches the worm.", 113 | "The pen is mightier than the sword." 114 | }; 115 | 116 | // Split the sentence into an array of words 117 | // and select those whose first letter is a vowel. 118 | var earlyBirdQuery = 119 | from sentence in strings 120 | let words = sentence.Split(' ') 121 | from word in words 122 | let w = word.ToLower() 123 | where w[0] == 'a' || w[0] == 'e' 124 | || w[0] == 'i' || w[0] == 'o' 125 | || w[0] == 'u' 126 | select word; 127 | 128 | // Execute the query. 129 | foreach (var v in earlyBirdQuery) 130 | { 131 | Console.WriteLine("\"{0}\" starts with a vowel", v); 132 | } 133 | ``` 134 | 135 | ## When should you use the query syntax or the API/method syntax? 136 | Ultimately the answer is which-ever way results in your writing your code quickly and in the most 137 | maintainable way. 138 | 139 | Consider use of the query syntax when: 140 | - You need to scope variables within your queries due to complexity 141 | - Your existing codebase already uses the query syntax 142 | - You prefer the query syntax and it won’t distract from your codebase 143 | 144 | Consider the use of the API/method syntax when 145 | 146 | - You have no need to scope variables within your queries 147 | - Your existing codebase already uses the API syntax 148 | - You prefer the API syntax and it won’t distract from your codebase 149 | 150 | Ultimately the query syntax is broken down into method/api calls so there is no performance difference, it 151 | is purely about code readability and matintainability. 152 | 153 | ## LINQ Samples 154 | 155 | For a truly comprehensive list of LINQ samples, visit [101 LINQ Samples](https://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b). 156 | You can also see the same samples using the API syntax along with extensive use of lambda expressions on 157 | [Frode Nilsen's Blog](http://linq101.nilzorblog.com/linq101-lambda.php) 158 | 159 | ### Use of `Where`, `Select`, and `Aggregate` 160 | 161 | ```cs 162 | // Filtering a list of dogs to get the German Shepards 163 | var germanShepards = dogs.Where(dog => dog.Breed == DogBreed.GermanShepard); 164 | 165 | // The same, using the query syntax 166 | var queryGermanShepards = from dog in dogs 167 | where dog.Breed == DogBreed.GermanShepard 168 | select dog; 169 | 170 | // Use the method of dog called TurnIntoACat to turn a list of dogs into cats 171 | var cats = dogs.Select(dog => dog.TurnIntoACat()); 172 | 173 | // Using the query syntax 174 | var queryCats = from dog in dogs 175 | select dog.TurnIntoACat(); 176 | 177 | // Sum the lengths of a list of strings using Aggregate 178 | int seed = 0; 179 | int sumOfStrings = strings.Aggregate(seed, (s1, s2) => s1.Length + s2.Length); 180 | ``` 181 | 182 | ### Union between two sets (using a custom comparator): 183 | 184 | ```cs 185 | // Define the logic to be used to determine if two breeds of dog have 186 | // equal hair length 187 | public class DogHairLengthComparer : IEqualityComparer 188 | { 189 | public bool Equals(Dog a, Dog b) 190 | { 191 | if (a == null && a == null) 192 | { 193 | return true; 194 | } 195 | else if ((a == null && b != null) || 196 | (a != null && b == null)) 197 | { 198 | return false; 199 | } 200 | else 201 | { 202 | return a.HairLengthType == b.HairLengthType; 203 | } 204 | } 205 | 206 | public int GetHashCode(Dog d) 207 | { 208 | // default hashcode is enough here, as these are simple objects. 209 | return b.GetHashCode(); 210 | } 211 | } 212 | 213 | ... 214 | 215 | // Now get all the short-haired dogs living in either of two different kennels 216 | var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer()) 217 | .Where(dog => dog.HairLengthType = DogHairLengthType.Short); 218 | 219 | ``` 220 | 221 | * Intersection between two sets: 222 | 223 | ```cs 224 | // Get the list of volunteers who give time at both charity1 and charity2. 225 | var volunteers = charity1.Volunteers.Intersect(charity2.Volunteers, 226 | new VolunteerTimeComparer()); 227 | ``` 228 | 229 | * Ordering: 230 | 231 | ```cs 232 | // Return a collection of driving directions but put the routes without tolls first even if 233 | // they are slower. 234 | var results = DirectionsProcessor.GetDirections(start, end) 235 | .OrderBy(direction => direction.HasNoTolls) 236 | .ThenBy(direction => direction.EstimatedTime); 237 | 238 | ``` 239 | Note that `OrderBy` is ascending. There is also an equivalent `OrderByDescending` method 240 | 241 | ## PLINQ 242 | 243 | PLINQ, or Parallel LINQ, is a parallel execution engine for LINQ expressions. In other words it allows a regular 244 | LINQ expression to be trivially parallelized across any number of threads. This is accomplished via a 245 | call to `AsParallel()` preceding the expression. 246 | 247 | Consider the following: 248 | 249 | ```cs 250 | public static string GetAllFacebookUserLikesMessage(IEnumerable facebookUsers) 251 | { 252 | var seed = default(UInt64); 253 | 254 | Func threadAccumulator = (t1, t2) => t1 + t2; 255 | Func threadResultAccumulator = (t1, t2) => t1 + t2; 256 | Func resultSelector = total => $"Facebook has {total} likes!"; 257 | 258 | return facebookUsers.AsParallel() 259 | .Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector); 260 | } 261 | 262 | ``` 263 | 264 | This code will partition `facebookUsers` across system threads as necessary, sum up the total 265 | likes on each thread in parallel, sum the results computed by each thread, and project that result 266 | into a human readable string. 267 | 268 | Parallelizable CPU-bound jobs which can be easily expressed via LINQ (in other words, ones that are pure functions 269 | and have no side effects) are a great candidate for PLINQ. For jobs which _do_ have a side effect, 270 | consider using the [Task Parallel Library](https://msdn.microsoft.com/en-us/library/dd460717.aspx). 271 | 272 | ## Exercises 273 | 1. Take the following paragraph and select the words that do not contain an 'e' `A penny saved is a penny earned. The early bird catches the worm. The pen is mightier than the sword."` 274 | 2. Sort the words in the paragraph displaying the short ones first 275 | 3. Take the list of people from [Tutorial 4](../004-Properties/Ex04/Program.cs) and display them in age order, youngest first. 276 | 4. Display the age of the oldest person in the list. 277 | 278 | ## Summary 279 | In this tutorial you learned about the LINQ syntax introduced to C# 3.0 and saw how the same syntax is able to be 280 | used to query any `Enumerable` data source. You learned about the different ways of specifying a query and when 281 | one might be more expressive than another. 282 | 283 | ## Additional Information 284 | - [System.Linq Enumerable Methods](https://msdn.microsoft.com/library/system.linq.enumerable_methods.aspx) 285 | - [101 LINQ Samples](https://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b) 286 | - [Linqpad](https://www.linqpad.net/), a playground environment and Database querying engine for C#/F#/VB 287 | - [EduLinq](http://codeblog.jonskeet.uk/2011/02/23/reimplementing-linq-to-objects-part-45-conclusion-and-list-of-posts/), an e-book for learning how LINQ-to-objects is implemented 288 | 289 | 290 | --- 291 | - Next: [Tutorial 7 - Asynchronous Programming](../007-Async/) 292 | - Previous: [Tutorial 5 - Delegates and Lambda Expressions](../005-Lambdas/) 293 | - Back to [Table of Contents](../README.md) 294 | 295 | -------------------------------------------------------------------------------- /007-Async/Ex01/DoSomething.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace ConsoleApplication 6 | { 7 | public class DoSomething 8 | { 9 | public int Slow() 10 | { 11 | Console.WriteLine("Start: Slow operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 12 | Thread.Sleep(5000); 13 | Console.WriteLine("End: Slow operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 14 | return 42; 15 | } 16 | 17 | public int Fast() 18 | { 19 | Console.WriteLine("Start: Fast operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 20 | int[] fib = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 }; 21 | foreach (int element in fib) 22 | { 23 | System.Console.WriteLine(element); 24 | } 25 | Console.WriteLine("End: Fast operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 26 | return 1; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /007-Async/Ex01/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ConsoleApplication 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | var doSomething = new DoSomething(); 10 | int slowResult = doSomething.Slow(); 11 | int fastResult = doSomething.Fast(); 12 | Console.WriteLine("The result from the slow task is {0}", slowResult); 13 | Console.WriteLine("The result from the fast task is {0}", fastResult); 14 | Console.WriteLine("Done."); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /007-Async/Ex01/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": { 8 | "System.Threading.Tasks": "4.3.0", 9 | "System.Threading.Thread": "4.3.0" 10 | }, 11 | "frameworks": { 12 | "netcoreapp1.1": { 13 | "dependencies": { 14 | "Microsoft.NETCore.App": { 15 | "type": "platform", 16 | "version": "1.1.0" 17 | } 18 | }, 19 | "imports": "dnxcore50" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /007-Async/Ex02/DoSomething.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace ConsoleApplication 6 | { 7 | public class DoSomething 8 | { 9 | public int Slow() 10 | { 11 | Console.WriteLine("Start: Slow operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 12 | Thread.Sleep(5000); 13 | Console.WriteLine("End: Slow operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 14 | return 42; 15 | } 16 | 17 | public async Task SlowAsync() 18 | { 19 | Console.WriteLine("Start: Slow operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 20 | await Task.Delay(5000); 21 | Console.WriteLine("End: Slow operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 22 | return 42; 23 | } 24 | 25 | public int Fast() 26 | { 27 | Console.WriteLine("Start: Fast operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 28 | int[] fib = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 }; 29 | foreach (int element in fib) 30 | { 31 | System.Console.WriteLine(element); 32 | } 33 | Console.WriteLine("End: Fast operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 34 | return 1; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /007-Async/Ex02/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var doSomething = new DoSomething(); 11 | Task slowTask = doSomething.SlowAsync(); 12 | int fastResult = doSomething.Fast(); 13 | Console.WriteLine("The result from the slow task is {0}", slowTask.Result); 14 | Console.WriteLine("The result from the fast task is {0}", fastResult); 15 | Console.WriteLine("Done"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /007-Async/Ex02/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": { 8 | "System.Threading.Tasks": "4.3.0", 9 | "System.Threading.Thread": "4.3.0" 10 | }, 11 | "frameworks": { 12 | "netcoreapp1.1": { 13 | "dependencies": { 14 | "Microsoft.NETCore.App": { 15 | "type": "platform", 16 | "version": "1.1.0" 17 | } 18 | }, 19 | "imports": "dnxcore50" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /007-Async/Ex03/DoSomething.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace ConsoleApplication 6 | { 7 | public class DoSomething 8 | { 9 | public int Slow() 10 | { 11 | Console.WriteLine("Start: Slow operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 12 | Thread.Sleep(5000); 13 | Console.WriteLine("End: Slow operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 14 | return 42; 15 | } 16 | 17 | public async Task SlowAsync() 18 | { 19 | Console.WriteLine("Start: Slow operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 20 | await Task.Delay(100); 21 | Console.WriteLine("End: Slow operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 22 | return 42; 23 | } 24 | 25 | public int Fast() 26 | { 27 | Console.WriteLine("Start: Fast operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 28 | for (int i = 1; i < 5000; i++) 29 | { 30 | System.Console.WriteLine(i); 31 | } 32 | Console.WriteLine("End: Fast operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 33 | return 1; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /007-Async/Ex03/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace ConsoleApplication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var doSomething = new DoSomething(); 11 | Task slowTask = doSomething.SlowAsync(); 12 | int fastResult = doSomething.Fast(); 13 | Console.WriteLine("The result from the slow task is {0}", slowTask.Result); 14 | Console.WriteLine("The result from the fast task is {0}", fastResult); 15 | Console.WriteLine("Done"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /007-Async/Ex03/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "debugType": "portable", 5 | "emitEntryPoint": true 6 | }, 7 | "dependencies": { 8 | "System.Threading.Tasks": "4.3.0", 9 | "System.Threading.Thread": "4.3.0" 10 | }, 11 | "frameworks": { 12 | "netcoreapp1.1": { 13 | "dependencies": { 14 | "Microsoft.NETCore.App": { 15 | "type": "platform", 16 | "version": "1.1.0" 17 | } 18 | }, 19 | "imports": "dnxcore50" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /007-Async/README.md: -------------------------------------------------------------------------------- 1 | # Asynchronous Programming in .NET 2 | This file is part of a multi-series workshop on learning C# on Linux available [here](../README.md). 3 | 4 | Authors: [Phillip Carter](https://github.com/cartermp), [Ken Chen](https://github.com/chenkennt), [Martin Woodward](https://github.com/martinwoodward) 5 | 6 | Async programming is a first-class concept within .NET, with async support in the runtime, 7 | the framework libraries, and .NET language constructs including in C#. Internally, they are based off of objects 8 | (such as `Task`) which take advantage of the underlying operating system to perform I/O-bound jobs 9 | as efficiently as possible. In client applications this is useful to ensure the UI thread remains responsive 10 | and for server applications having first class native support for asynchronous programming is a key factor in 11 | helping developers build scalable web applications. 12 | 13 | ## `async` in C# 14 | 15 | The C# language-level asynchronous programming model follows what is known as the 16 | [Task-based Asynchronous Pattern (TAP)](https://msdn.microsoft.com/en-us/library/hh873175.aspx). 17 | The core of TAP are the `Task` and `Task` objects, which model asynchronous operations, supported 18 | by the `async` and `await` keywords (`Async` and `Await` in VB), which provide a natural developer experience 19 | for interacting with Tasks. The result is the ability to write expressive asynchronous code, as opposed to 20 | callback methods which often do not express the intent as cleanly. There are other ways to approach 21 | async code than `async` and `await` outlined in the TAP article linked above, but this tutorial will focus 22 | on the language-level constructs in C#. 23 | 24 | For example, you may need to download some data from a remote web service when a button is pressed, 25 | but don’t want to block the UI thread. It can be accomplished simply like this: 26 | 27 | ```cs 28 | private readonly HttpClient _httpClient = new HttpClient(); 29 | 30 | ... 31 | 32 | button.Clicked += async (o, e) => 33 | { 34 | var stringData = await _httpClient.DownloadStringAsync(URL); 35 | DoStuff(stringData); 36 | }; 37 | 38 | ``` 39 | 40 | And that’s it! The code expresses the intent (downloading some data asynchronously) without getting bogged down 41 | in interacting with Task objects. 42 | 43 | For the theoretically-inclined, this is an implementation of the 44 | [Future/Promise concurrency model](https://en.wikipedia.org/wiki/Futures_and_promises). 45 | 46 | A few important things to know before continuing: 47 | 48 | * Async code uses the `Task` and `Task` types, which are constructs used to model the work being done in 49 | an asynchronous context. 50 | * When the `await` keyword is applied, it suspends the calling method and yields control back to its caller 51 | until the awaited task is complete. This is what allows a UI to be responsive and a service to be elastic. 52 | * `await` can only be used inside an async method. 53 | * Unless an async method has an `await` inside its body, it will never yield! 54 | * `async void` should **only** be used on Event Handlers (where it is required). 55 | 56 | ## Example 57 | 58 | The following example shows how to write basic async code for both a client app and a web service. The code, 59 | in both cases, will count the number of times ”.NET” appears in the HTML of “dotnetfoundation.org”. 60 | 61 | ##### Client application snippet: 62 | 63 | ```cs 64 | private readonly HttpClient _httpClient = new HttpClient(); 65 | 66 | private async void SeeTheDotNets_Click(object sender, RoutedEventArgs e) 67 | { 68 | // Capture the task handle here so we can await the background task later. 69 | var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("http://www.dotnetfoundation.org"); 70 | 71 | // Any other work on the UI thread can be done here, such as enabling a Progress Bar. 72 | // This is important to do here, before the "await" call, so that the user 73 | // sees the progress bar before execution of this method is yielded. 74 | NetworkProgressBar.IsEnabled = true; 75 | NetworkProgressBar.Visibility = Visibility.Visible; 76 | 77 | // The await operator suspends SeeTheDotNets_Click, returning control to its caller. 78 | // This is what allows the app to be responsive and not hang on the UI thread. 79 | var html = await getDotNetFoundationHtmlTask; 80 | int count = Regex.Matches(html, ".NET").Count; 81 | 82 | DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}"; 83 | 84 | NetworkProgressBar.IsEnabled = false; 85 | NetworkProgressBar.Visbility = Visibility.Collapsed; 86 | } 87 | 88 | ``` 89 | 90 | ##### Web service snippet (ASP.NET MVC): 91 | 92 | ```cs 93 | private readonly HttpClient _httpClient = new HttpClient(); 94 | 95 | [HttpGet] 96 | [Route("DotNetCount")] 97 | public async Task GetDotNetCountAsync() 98 | { 99 | // Suspends GetDotNetCountAsync() to allow the caller (the web server) 100 | // to accept another request, rather than blocking on this one. 101 | var html = await _httpClient.GetStringAsync("http://dotnetfoundation.org"); 102 | 103 | return Regex.Matches(html, ".NET").Count; 104 | } 105 | 106 | ``` 107 | ##### Console application snippet: 108 | ```cs 109 | using System; 110 | using System.Net.Http; 111 | using System.Text.RegularExpressions; 112 | using System.Threading.Tasks; 113 | 114 | namespace ConsoleApplication 115 | { 116 | public class Program 117 | { 118 | public static readonly string ADDRESS = "http://dotnetfoundation.org"; 119 | public static int Main(string[] args) 120 | { 121 | HttpClient httpClient = new HttpClient(); 122 | Task getStringTask = Task.Run(async() => 123 | { 124 | return await httpClient.GetStringAsync(ADDRESS); 125 | }); 126 | Console.WriteLine("Connecting to {0}...", ADDRESS); 127 | Console.WriteLine("This may take a moment"); 128 | Console.WriteLine("Number of times \".NET\" used: {0}", 129 | Regex.Matches(getStringTask.Result, ".NET").Count); 130 | return 1; 131 | } 132 | } 133 | } 134 | 135 | ``` 136 | 137 | ## More on Task and Task 138 | 139 | As mentioned before, Tasks are constructs used to represent operations working in the background. 140 | 141 | * `Task` represents a single operation which does not return a value. 142 | * `Task` represents a single operation which returns a value of type `T`. 143 | 144 | Tasks are awaitable, meaning that at the `await` will allow your application or service to perform useful 145 | work while the task is running by yielding control to its caller until the task is done. 146 | If you’re using `Task`, the `await` keyword will additionally “unwrap” the value returned when the Task is 147 | complete. 148 | 149 | It’s important to reason about Tasks as abstractions of work happening in the background, and _not_ an 150 | abstraction over multithreading. In fact, unless explicitly started on a new thread via `Task.Run`, a Task will 151 | **start on the current thread and delegate work to the Operating System**. 152 | 153 | ## Typical lifecycle of an `async call` 154 | To understand the detail of what happens during an async call let's examine the example previously. 155 | 156 | The call (such as `GetStringAsync` from `HttpClient`) makes its way through the .NET libraries and runtime 157 | until it reaches a system interop call (such as `P/Invoke` to call out to the underling OS). 158 | This eventually makes the proper System API call (such as `write()` to a socket file descriptor on Linux). 159 | That System API call is then dealt with in the kernel, where the I/O request is sent to the proper subsystem. 160 | Although details about scheduling the work on the appropriate device driver are different for each OS, 161 | eventually an “incomplete task” signal will be sent from the device driver, bubbling its way back up to the .NET 162 | runtime. This will be converted into a `Task` or `Task` by the runtime and returned to the calling method. 163 | When `await` is encountered, execuction is yielded and the system will continue in the calling method 164 | while the Task is running. 165 | 166 | When the device driver has the data, it sends an interrupt which eventually allows the OS to bubble the 167 | result back up to the runtime, which will the queue up the result of the Task. 168 | Eventually execution will return to the method which called `GetStringAsync` at the `await`, 169 | and will “unwrap” the return value from the `Task` which was being awaited. 170 | The method now has the result! 171 | 172 | Although many details were glossed over here (such as how “borrowing” compute time on a thread pool is coordinated), 173 | the important thing to recognize here is that this is an interrupt based system and **no thread is 174 | 100% dedicated to running the initiated task**. This allows threads in the thread pool of a system to handle 175 | a larger volume of work rather than having to wait for I/O to finish. 176 | 177 | Although the above may seem like a lot of work to be done, when measured in terms of wall clock time, 178 | it’s miniscule compared to the time it takes to do the actual typical I/O operation. 179 | 180 | Tasks are also used outside of the async programming model. They are the foundation of the Task Parallel Library, 181 | which supports the parallelization of CPU-bound work via 182 | [Data Parallelism](https://msdn.microsoft.com/en-us/library/dd537608.aspx) and 183 | [Task Parallelism](https://msdn.microsoft.com/en-us/library/dd537609.aspx). 184 | 185 | ## Important Information and Advice 186 | 187 | Although async programming is relatively straightforward in C#, there are some rules to keep in mind which 188 | can prevent unexpected behavior. 189 | 190 | ### You should add `Async` as the suffix of every async method name you write. 191 | 192 | This is the naming convention used in .NET to more-easily differentiate synchronous and asynchronous methods. 193 | Note that certain methods which aren’t explicitly called by your code (such as event handlers or 194 | web controller methods) don’t necessarily apply. Because these are not explicitly called by your code, 195 | being explicit about their naming isn’t as important. 196 | 197 | ### `async void` **should only be used for event handlers. 198 | 199 | `async void` is the only way to allow asynchronous event handlers to work because events do not have 200 | return types (thus cannot make use of `Task` and `Task`). Any other use of `async void` does not follow 201 | the TAP model and can be challenging to use, such as: 202 | 203 | * Exceptions thrown in an `async void` method can’t be caught outside of that method. 204 | * `async void` methods are very difficult to test. 205 | * `async void` methods can cause bad side effects if the caller isn’t expecting them to be async. 206 | 207 | ### Tread carefully when using async lambdas in LINQ expressions 208 | 209 | I know we've only just learned them and the temptation will be to used all these new tricks at ones but Lambda 210 | expressions in LINQ use deferred execution, meaning code could end up executing at a time when you’re not 211 | expecting it to. The introduction of blocking tasks into this can easily result in a deadlock if not written 212 | correctly. Additionally, the nesting of asynchronous code like this can also make it more difficult to reason 213 | about the execution of the code. Async and LINQ are powerful, but should be used together as carefully 214 | and clearly as possible. 215 | 216 | ### Write code that awaits Tasks in a non-blocking manner 217 | 218 | Blocking the current thread as a means to wait for a Task to complete can result in deadlocks and blocked context 219 | threads, and can require significantly more complex error-handling. The following table provides guidance on how 220 | to deal with waiting for Tasks in a non-blocking way: 221 | 222 | | Use this... | Instead of this... | When wishing to do this | 223 | | -------------------- | ------------------ | ----------------------- | 224 | | `await` | `Task.Wait` or `Task.Result` | Retrieving the result of a background task | 225 | | `await Task.WhenAny` | `Task.WaitAny` | Waiting for any task to complete | 226 | | `await Task.WhenAll` | `Task.WaitAll` | Waiting for all tasks to complete | 227 | | `await Task.Delay` | `Thread.Sleep` | Waiting for a period of time | 228 | 229 | ### Write less stateful code 230 | 231 | Don’t depend on the state of global objects or the execution of certain methods. Instead, depend only on the 232 | return values of methods. Why? 233 | 234 | * Code will be easier to reason about. 235 | * Code will be easier to test. 236 | * Mixing async and synchronous code is far simpler. 237 | * Race conditions can typically be avoided altogether. 238 | * Depending on return values makes coordinating async code simple. 239 | * (Bonus) it works really well with dependency injection. 240 | 241 | A recommended goal is to achieve complete or near-complete 242 | [Referential Transparency](https://en.wikipedia.org/wiki/Referential_transparency_%28computer_science%29) 243 | in your code. Doing so will result in an extremely predictable, testable, and maintainable codebase. 244 | 245 | ## Exercises 246 | 1. Write a console application that initializes a new `DoSomething` class below, executes the 247 | `DoSomething.Slow()` method then the `DoSomething.Fast()` method and then displays the result 248 | `Slow()` on one line followed by the result of `Fast()` on the next. Run the application and 249 | observe the results. 250 | 251 | 2. Create a new `DoSomething.SlowAsync()` method that does the same as the `Slow()` method below 252 | but instead of `Thread.Sleep(5000);` waits for 5 seconds using the following code instead 253 | `await Task.Delay(5000);`. Modify your application to call the new Async version instead, 254 | execute and observe the results. 255 | 256 | 3. Adjust the amount of delay in the `Slow()` method and increase the work done in the `Fast()` method 257 | until you observe the fast method thread finishing after the slow method thread and observe the 258 | behaviour. 259 | 260 | **Sample Code** 261 | ```c# 262 | using System; 263 | using System.Threading; 264 | using System.Threading.Tasks; 265 | 266 | namespace ConsoleApplication 267 | { 268 | public class DoSomething 269 | { 270 | public int Slow() 271 | { 272 | Console.WriteLine("Start: Slow operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 273 | Thread.Sleep(5000); 274 | Console.WriteLine("End: Slow operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 275 | return 42; 276 | } 277 | 278 | public int Fast() 279 | { 280 | Console.WriteLine("Start: Fast operation on thread {0}", Thread.CurrentThread.ManagedThreadId); 281 | int[] fib = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 }; 282 | foreach (int element in fib) 283 | { 284 | System.Console.WriteLine(element); 285 | } 286 | Console.WriteLine("End: Fast operation on thread id {0}", Thread.CurrentThread.ManagedThreadId); 287 | return 1; 288 | } 289 | } 290 | } 291 | 292 | ``` 293 | **Dependencies** 294 | For the exercises, you will also need the following dependencies in your project.json 295 | ``` 296 | "dependencies": { 297 | "System.Threading.Tasks": "4.3.0", 298 | "System.Threading.Thread": "4.3.0" 299 | }, 300 | ``` 301 | 302 | ## Summary 303 | The use of `async` and `await` in C# helps .NET optimize the usage of processing threads and allows your application 304 | to feel more responsive to users and to have better scalability characteristics without introducing significant 305 | additional complexity to your code. The .NET framework takes care of thread pooling and thread synchronization for the developer 306 | but you still have to be explicit with your code when you want to make use of asynchronous programming. Consider 307 | using Async when calling any potentially long running I/O operations or other long, non-CPU bound activities. 308 | 309 | ## Additional Information 310 | - [Async/Await Reference Docs](https://msdn.microsoft.com/en-us/library/hh191443.aspx) 311 | - [Tasks and the Task Parallel Library](https://msdn.microsoft.com/en-us/library/dd460717.aspx) 312 | - [Async Programming : Introduction to Async/Await on ASP.NET](https://msdn.microsoft.com/en-gb/magazine/dn802603.aspx) 313 | 314 | 315 | --- 316 | - Next: [Tutorial 8 - Web Programming with ASP.NET Core](../008-Asp.net/) 317 | - Previous: [Tutorial 6 - Linq](../006-Linq/) 318 | - Back to [Table of Contents](../README.md) 319 | 320 | -------------------------------------------------------------------------------- /008-Asp.net/README.md: -------------------------------------------------------------------------------- 1 | # Web Programming with ASP.NET Core 2 | This file is part of a multi-series workshop on learning C# on Linux available [here](../README.md). 3 | 4 | Author: [Bill Wagner](https://github.com/BillWagner), [Martin Woodward](https://github.com/martinwoodward) 5 | 6 | ## Getting Started 7 | The easiest way to get started with a new ASP.NET Core app is to go into a new directory and then use the `dotnet new -t web` 8 | command to specify that you want to create a new application with the web template. 9 | ``` 10 | dotnet new -t web 11 | dotnet restore 12 | dotnet run 13 | ``` 14 | 15 | By default, a new ASP.NET application listens on port 5000. Therefore if you open a browser on http://localhost:5000 you should be 16 | able to see your new website. 17 | 18 | ## Web Applications Basics 19 | The first thing to understand is that in .NET Core, and ASP.NET web application is just a console application. If you open `Program.cs` you can examine the Main method to see how the application runs the WebHost. You can easily modify this, for example if you want the web server to listen on port 8080 on all interfaces rather than just port 5000 on localhost you could modify your Main method as follows 20 | ``` 21 | public static void Main(string[] args) 22 | { 23 | var host = new WebHostBuilder() 24 | .UseKestrel() 25 | .UseContentRoot(Directory.GetCurrentDirectory()) 26 | .UseIISIntegration() 27 | .UseStartup() 28 | .UseUrls("http://*:8080") 29 | .Build(); 30 | host.Run(); 31 | } 32 | ``` 33 | If you take a look at `project.json` you'll also see that this contains much more information now. There are many more dependencies now, but also the publish options contain additional information on how the application should be prepared when performing a `dotnet publish` in order to deploy. 34 | 35 | The `web.config` file contains basic configuration information for the web application and is parsed by framework libraries in ASP.NET. 36 | 37 | We can also take a look at the additional code in `Startup.cs`. This startup code enables other features in ASP.NET that are part of this web application. Within the `Configure` method, there’s a call to `UseStaticFiles()`. This method enables delivery of static files like images or javascript files from the web server file system. Second, there’s a call to `UseMvc()`, which enables the MVC framework in this application: 38 | ```c# 39 | app.UseMvc(routes => 40 | { 41 | routes.MapRoute( 42 | name: "default", 43 | template: "{controller=Home}/{action=Index}/{id?}"); 44 | }); 45 | ``` 46 | 47 | The call to `MapRoute()` configures a mapping from the URL request to code in your application. The sample creates the default mapping. The template is how the MVC core classes map a URL to code. This route maps a URL of the form 48 | `http://mywebapplication.com/Catalog/List/12` To a class, and a method with a single parameter. 49 | 50 | The first portion of the URL after the host, `Catalog` defines the name of a Controller class. That maps to a class in the Controller directory of the default project. The default mapping defines the default controller as the `Home` controller. By convention, controller classes must end in the test `Controller`. The default controller is a class named `HomeController`. 51 | 52 | The second portion of the route maps to a method name in that controller. The default method is a method named `Index`. 53 | 54 | The final portion maps to a single argument, named `id`. The `?` defines that this argument is optional. If the argument is supplied, it would be converted to the type specified on the Controller method. 55 | 56 | Next, let’s look at the new files created by this template. We’ll start with `gulpfile.js`. This file defines many of the build tasks related to client side assets (javascript, css files). As you build more ASP.NET projects, you’ll learn that ASP.NET uses standard web tools for many client side web development tasks. 57 | 58 | The other major sections are the `Controllers` directory, and the `Views` directory. The controller directory has the `HomeController` class. The `HomeController` provides an example of an MVC Controller. A Controller is responsible for generating the response from an HTTP request. Notice that all the methods in the HomeController end with a call to `View()`. The `View()` method is a member of the Controller base class. This method generates the HTML response by looking for a View template, which we’ll discuss shortly. You can see the four methods that map to the four URLs that are configured in this application. 59 | 60 | The `Views` folder contains the templates that generate the HTML views. The Views are HTML templates that are processed by the MVC Razor engine. The design enables a great deal of reuse in the HTML that you’ll use in a website. The View engine processes the view templates. The `Shared` sub-directory under the `Views` directory contains View templates that contain the HTML that is shared across all the pages in your application. In the `_Layout.cshtml` file, you’ll see the shared layout, with sections that are placeholders for the views for the specific pages. The views for each page are in a subdirectory of the same name as the controller (e.g. `Home`). The names of the view files match the name of the method in the controller (e.g. `Index`, `About`). 61 | 62 | You can see that the MVC framework follows these conventions to make it easier to create an application with a minimum of extra configuration. 63 | 64 | The remaining assets are the images and css styles that are part of the starter application. 65 | 66 | ## Yeoman Scaffolding 67 | While the `dotnet` command has some basic templating features, and more are being added all the time you can also make use 68 | of the popular Yeoman tool to help you scaffold and ASP.NET application. 69 | 70 | You’ll need to install yeoman, bower, the grunt CLI and gulp. Then 71 | install the ASP.NET website generator template to get started quickly 72 | ``` 73 | sudo npm install -g yo bower grunt-cli gulp 74 | sudo npm install -g generator-aspnet 75 | ``` 76 | 77 | You can generated an ASP.NET core site by running the yeoman generator: 78 | ``` 79 | yo aspnet 80 | ``` 81 | This give you the option to build several different starter applications. One of the great features of the ASP.NET 82 | ecosystem is that the tools generate boiler plate example code for many common scenarios. The downside of this boilerplate code is that it is easy to create an application that has code you don’t need for features that aren’t 83 | intended for your site. 84 | 85 | ## Summary 86 | In this tutorial we explored ASP.NET Core and examined the default starter template. For more information, [Jon Galloway has an extensive workshop on ASP.NET](https://github.com/jongalloway/aspnetcore-workshop). 87 | 88 | ## Additional Information 89 | - [ASP.NET Workshop](https://github.com/jongalloway/aspnetcore-workshop) 90 | - [ASP.NET Documentation](https://docs.asp.net) 91 | - [Visual Studio Code](https://code.visualstudio.com/) 92 | - [OmniSharp](http://www.omnisharp.net/) 93 | 94 | --- 95 | - Previous: [Tutorial 7 - Asynchronous Programming](../007-Async/) 96 | - Back to [Table of Contents](../README.md) 97 | 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Martin Woodward 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Workshop: Learning C# on Linux 2 | 3 | This repository contains the content for a workshop I am putting together 4 | on Learning C# on Linux using .NET Core. This is a work in progress 5 | so feel free to contribute. 6 | 7 | Also take a look at the official Microsoft [C# Interactive Tutorial](https://www.microsoft.com/net/tutorials/csharp/getting-started). 8 | Note that the online tutorial contains an interactive playground environment allowing you to try out the 9 | majority of the C# code in these exercises as well is you don't wish to install the `dotnet` environment on your machine. 10 | 11 | A copy of the introductory slide deck accompanying this tutorial is available [here](https://1drv.ms/p/s!AnoZDWDiiqDHxd84US2gj0Kp_DAT4A). 12 | 13 | ## Learning C# on Linux 14 | C# is a modern, advanced general purpose programming language and is now fully open source and supported on Linux. 15 | In this short workshop you will learn how to be productive with the new modern C# on Linux, 16 | what it is like building high performance web workloads in ASP.NET and what tooling support you have available. 17 | 18 | **Target Audience:** This workshop is aimed at developers with no prior knowledge of C# 19 | but have some experience with other programming languages or would like a refresher on the modern C# language. 20 | 21 | ## Workshop Contents 22 | 23 | 1. [Getting Started](001-Getting-Started/) - Installing pre-requisites and getting .NET Core installed on your system 24 | 2. [Hello C# World](002-Hello-CSharp/) - Learn the structure of a basic C# program 25 | 3. [C# Language Basics](003-Language-Basics/) - Learn the basics of the C# language such as variables, expressions, 26 | control flow, loops, type system, inheritance, generics and more. 27 | 4. [Properties](004-Properties/) - Defining properties in C# classes 28 | 5. [Delegates and Lambda expressions](005-Lambdas/) - Understanding delegates and lambda expressions in C# 29 | 6. [LINQ](006-Linq/) - An introduction to Language Integrated Query (LINQ) 30 | 7. [Asynchronous Programming](007-Async/) - How to use .NET's built in support for asynchronous programming 31 | 8. [ASP.NET](008-Asp.net/) - Building Web Applications with ASP.NET Core 32 | 33 | ## Additional Reading 34 | For more information and additional tutorials related to learning C# on Linux see the following. 35 | - [C# Interactive Tutorial](https://www.microsoft.com/net/tutorials/csharp/getting-started) 36 | - [.NET Core Documentation](https://docs.microsoft.com/en-us/dotnet/articles/core/) 37 | - [Web Applications with ASP.NET Core](http://docs.asp.net/en/latest/index.html) 38 | 39 | ## Books 40 | C# has many excellent books from many different authors. While .NET Core is still very new there 41 | are not many books about it yet specifically but if you are an experienced developer wanting to 42 | learn C# the following books are excellent choices to learn more: 43 | - [C# in Depth](http://amzn.to/1PUBo8A) by Jon Skeet 44 | - [Effective C#](http://amzn.to/1UK88zq) by Bill Wagner 45 | - [Professional ASP.NET MVC 5](http://amzn.to/1o6bh20) by Jon Galloway, Brad Wilson, K. Scott Allen, David Matson 46 | - [C#6.0 and the .NET 4.6 Framework](http://amzn.to/1PUC8uh) by Andrew Troelsen and Philip Japikse 47 | 48 | --------------------------------------------------------------------------------