├── .gitignore ├── LICENSE ├── README.md ├── csharp-tutorial.sln └── csharp-tutorial ├── 00_General.cs ├── 01_Generics.cs ├── 02_ReferenceValueType.cs ├── 03_Null.cs ├── 04_ExtensionMethods.cs ├── 05_Delegates.cs ├── 06_Anonymous.cs ├── 07_Linq.cs ├── 08_Composition.cs ├── 09_Tasks.cs ├── 10_Async.cs ├── 11_Closures.cs ├── 12_Event.cs ├── 13_Parallel.cs ├── 14_Generators.cs ├── 15_Lazy.cs ├── 16_PatternMatching.cs ├── 17_Tuples.cs ├── 18_Json.cs ├── Helpers ├── Hsl │ ├── HslJsonSample.cs │ └── HslSiriData.cs └── SensorData.cs └── csharp-tutorial.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tomi Tuhkanen 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 | # csharp-tutorial 2 | 3 | Before this tutorial participant should be familair with programming environments and basics of C# language. Deeper knowledge of any programming language is recommended. 4 | 5 | ### Before 6 | 7 | 1. Getting started with C# and VS / VS Code 8 | * [Become familiar with tools](https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/intro-to-csharp/local-environment) 9 | * [Getting started with VS Code](https://docs.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio-code) 10 | * [Build app with Visual Studio](https://docs.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio) 11 | * [Build full solution](https://docs.microsoft.com/en-us/dotnet/core/tutorials/using-on-mac-vs-full-solution) 12 | 2. Read e.g. [C# Yellow Book](http://www.csharpcourse.com/) 13 | 3. Do [C# Koans](https://github.com/NotMyself/DotNetCoreKoans) 14 | 15 | 16 | #### After (advanced material) 17 | 18 | * LINQ 19 | * [LINQ-Tutorial](https://github.com/Basware/LINQ-Tutorial) 20 | * Reactive Extensions 21 | * [The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) 22 | * [C# Reactive Extensions (Rx) Koans](https://github.com/ttu/csharp-rx-koans) 23 | -------------------------------------------------------------------------------- /csharp-tutorial.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2003 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "csharp-tutorial", "csharp-tutorial\csharp-tutorial.csproj", "{17869326-2B83-4238-8E39-251B9820ED88}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7F6D60EB-82F1-44DA-9276-AB5921EB2D70}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {17869326-2B83-4238-8E39-251B9820ED88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {17869326-2B83-4238-8E39-251B9820ED88}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {17869326-2B83-4238-8E39-251B9820ED88}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {17869326-2B83-4238-8E39-251B9820ED88}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {61CC45F0-E4A0-487C-9E37-60CC92136AC6} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /csharp-tutorial/00_General.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace csharp_tutorial 4 | { 5 | public class General 6 | { 7 | public class Hello 8 | { 9 | // This can be only set in constructor 10 | // Use this as often as possible, as it is generally not recommended to use mutable class variables 11 | private readonly int _myBestValue; 12 | 13 | public Hello(int value) 14 | { 15 | GettableText = "Set me free"; 16 | _myBestValue = value; 17 | } 18 | 19 | // Getters and setters 20 | public string Text { get; set; } 21 | 22 | // Or can be only set in constructor (immutable) 23 | public string GettableText { get; } 24 | 25 | // Can have private setters 26 | public string PrivateText { get; private set; } 27 | 28 | // Backing filed for a preperty is a common case to use private variables 29 | private int _myValue; 30 | 31 | // Properties can have functionality 32 | public int CurrentValue 33 | { 34 | get => _myValue; 35 | set 36 | { 37 | if (_myValue != value) 38 | { 39 | PrivateText = $"My current value is {value}"; 40 | _myValue = value; 41 | // Usually here can be some on changeg event notifications etc. 42 | } 43 | } 44 | } 45 | 46 | // Read only properties 47 | public string HelloText => $"Hello! My current value is {_myValue}"; 48 | 49 | public string HelloTextOnce { get; } = $"Hello! My current value is 000"; 50 | 51 | // There is something different how these are handled, so latter one can only have reference to static fields 52 | 53 | // Simple functions 54 | public string GetText(string ending) => $"Hello {ending}"; 55 | 56 | public string GetText2(string ending) 57 | { 58 | return $"Hello {ending}"; 59 | } 60 | 61 | // Local functions 62 | public string GetName(string name) 63 | { 64 | string Capitalize(string toChange) 65 | { 66 | return toChange.Substring(0, 1).ToUpper() + toChange.Substring(1); 67 | } 68 | 69 | return Capitalize(name); 70 | } 71 | 72 | // When to use field vs property 73 | // When to use property vs method 74 | } 75 | 76 | [Fact] 77 | public void Hello_Samples() 78 | { 79 | var myValue = 1; 80 | 81 | var hello = new Hello(myValue); 82 | 83 | var helloText = hello.HelloText; 84 | 85 | var valueText = hello.PrivateText; 86 | 87 | var valueImmutable = hello.GettableText; 88 | 89 | var helloTimmy = hello.GetText("Timmy"); 90 | var helloJames = hello.GetText2("James"); 91 | 92 | var capitalized = hello.GetName("ramon"); 93 | 94 | Assert.Equal("Ramon", capitalized); 95 | } 96 | 97 | public class NormalClass 98 | { 99 | private int Hidden { get; set; } 100 | 101 | protected int VisibleToParent { get; set; } 102 | 103 | public int Visible { get; set; } 104 | } 105 | 106 | internal class VisibleOnlyInsideAssembly 107 | { } 108 | 109 | public class VisibleToAll 110 | { } 111 | } 112 | } -------------------------------------------------------------------------------- /csharp-tutorial/01_Generics.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | namespace csharp_tutorial 8 | { 9 | public class Generics 10 | { 11 | [Fact] 12 | public void Collections_Generic() 13 | { 14 | // Common place to use generics are collections 15 | 16 | var intList = new List { 1, 2, 3 }; 17 | intList.Add(4); 18 | 19 | var intArray = new[] { 1, 2, 3 }; 20 | 21 | var stringList = new List { "hello", "test", "ok" }; 22 | stringList.Add("NewItem"); 23 | 24 | var dict = new Dictionary 25 | { 26 | ["test"] = new User { Age = 30 }, 27 | ["unit"] = new User { Age = 25 }, 28 | }; 29 | 30 | dict.Add("integration", new User { Age = 50 }); 31 | 32 | // And like most languages, C# has also many ways to do a same things 33 | var dict2 = new Dictionary 34 | { 35 | { "test", new User { Age = 30 } }, 36 | { "unit", new User { Age = 25 } } 37 | }; 38 | 39 | IReadOnlyList readonlyList = new List { "hello" }; 40 | 41 | var list = new List { "hello" }; 42 | var reaondOnlyCollection = new ReadOnlyCollection(list); 43 | 44 | var occupationsMutable = new Dictionary 45 | { 46 | ["Malcolm"] = "Captain", 47 | ["Kaylee"] = "Mechanic" 48 | }; 49 | occupationsMutable["Jayne"] = "Public Relations"; 50 | occupationsMutable.Add("Rick", "Navigation"); 51 | } 52 | 53 | public class GenericFunctionsBag where T : class 54 | { 55 | private List _items; 56 | 57 | public int ItemCount => _items.Count; 58 | 59 | public void AddItem(T newItem) => _items.Add(newItem); 60 | 61 | public T GetDefault() => default(T); 62 | 63 | public J GetDefault() => default(J); 64 | 65 | public bool IsType(string json) => JsonConvert.DeserializeObject(json) != null; 66 | 67 | public T GetFirstItem(IEnumerable items) => items.First(); 68 | 69 | public J GetLast(IEnumerable items) => items.Last(); 70 | } 71 | 72 | [Fact] 73 | public void GenericsExamples() 74 | { 75 | var genericBag = new GenericFunctionsBag(); 76 | 77 | // Won't work, because of where T : class 78 | //var gb = new GenericFunctionsBag(); 79 | 80 | genericBag.AddItem(new User()); 81 | var count = genericBag.ItemCount; 82 | 83 | var defUser = genericBag.GetDefault(); 84 | var defInt = genericBag.GetDefault(); 85 | 86 | var isUser = genericBag.IsType("{ 'value' : 2 }"); 87 | Assert.False(isUser); 88 | 89 | isUser = genericBag.IsType("{ 'name' : 'jimmy' }"); 90 | Assert.True(isUser); 91 | } 92 | 93 | [Fact] 94 | public void Casting() 95 | { 96 | var admin = new Admin { Name = "Timmy" }; 97 | 98 | var user = new User { Name = "James" }; 99 | 100 | // Can't cast to more specific type 101 | // Safe casting with as 102 | var adminAsUser = admin as User; 103 | var willBeNull = user as PowerUser; 104 | 105 | // Unsafe casting 106 | var willThrow = (PowerUser)user; 107 | 108 | // Common use case 109 | void handleUser(User us) 110 | { 111 | if (us is Admin) { } 112 | else if (us is PowerUser) { } 113 | } 114 | } 115 | 116 | public IEnumerable OrderByAge(IEnumerable users) 117 | { 118 | // OrderBy comes from linq, more of that later 119 | return users.OrderBy(e => e.Age); 120 | } 121 | 122 | public IEnumerable OrderByAgeGeneric(IEnumerable users) where T : User 123 | { 124 | return users.OrderBy(e => e.Age); 125 | } 126 | 127 | [Fact] 128 | public void Function_Type_Specific() 129 | { 130 | // Common place to use generics are collections 131 | 132 | var admins = new List { new Admin { }, new Admin { } }; 133 | var powerUsers = new List { new PowerUser { }, new PowerUser { } }; 134 | 135 | var sortedAdmins = OrderByAge(admins); 136 | // sortedAdmins are now Users 137 | //sortedAdmins.First().Type 138 | 139 | var sortedAdminsGenerics = OrderByAgeGeneric(admins); 140 | // Now sortedAdmins are Admins 141 | var firstType = sortedAdminsGenerics.First().Type; 142 | } 143 | 144 | public class User 145 | { 146 | public string Name { get; set; } 147 | public int Age { get; set; } 148 | } 149 | 150 | public class PowerUser : User 151 | { 152 | public string Password { get; set; } 153 | } 154 | 155 | public class Admin : User 156 | { 157 | public string Type { get; set; } 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /csharp-tutorial/02_ReferenceValueType.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xunit; 3 | 4 | namespace csharp_tutorial 5 | { 6 | public class RefVal 7 | { 8 | /* 9 | * The Types in .NET Framework are either treated by Value Type or by Reference Type. 10 | * A Value Type holds the data within its own memory allocation and 11 | * a Reference Type contains a pointer to another memory location that holds the real data. 12 | * Reference Type variables are stored in the heap while Value Type variables are stored in the stack. 13 | */ 14 | 15 | // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/passing-parameters 16 | // Types are passed to methods by value 17 | // Passing a value-type variable to a method by value means passing a copy of the variable to the method 18 | // A variable of a reference type does not contain its data directly; it contains a reference to its data 19 | 20 | // http://net-informations.com/faq/general/valuetype-referencetype.htm 21 | 22 | // Coffee cup example 23 | // https://www.mathwarehouse.com/programming/passing-by-value-vs-by-reference-visual-explanation.php 24 | 25 | [Fact] 26 | public void Update_Value() 27 | { 28 | int original = 2; 29 | 30 | DoSomething(original); 31 | Assert.Equal(2, original); 32 | 33 | DoSomething(ref original); 34 | Assert.Equal(5, original); 35 | } 36 | 37 | private void DoSomething(int myValue) 38 | { 39 | myValue = 5; 40 | } 41 | 42 | private void DoSomething(ref int myValue) 43 | { 44 | myValue = 5; 45 | } 46 | 47 | public class Person 48 | { 49 | public string Name { get; set; } 50 | } 51 | 52 | [Fact] 53 | public void Update_Class() 54 | { 55 | var person = new Person { Name = "Harry" }; 56 | 57 | UpdatePerson(person); 58 | Assert.Equal("Harry", person.Name); 59 | 60 | UpdatePersonName(person); 61 | Assert.Equal("Timmy", person.Name); 62 | 63 | UpdatePerson(ref person); 64 | Assert.Equal("Sammy", person.Name); 65 | } 66 | 67 | private void UpdatePerson(Person person) 68 | { 69 | person = new Person { Name = "Sammy" }; 70 | } 71 | 72 | private void UpdatePersonName(Person person) 73 | { 74 | person.Name = "Timmy"; 75 | } 76 | 77 | private void UpdatePerson(ref Person person) 78 | { 79 | person = new Person { Name = "Sammy" }; 80 | } 81 | 82 | [Fact] 83 | public void Out() 84 | { 85 | void GetSome(out int value) 86 | { 87 | value = 5; 88 | } 89 | 90 | GetSome(out var myValue); 91 | 92 | Assert.Equal(5, myValue); 93 | } 94 | 95 | [Fact] 96 | public void Out_Collection() 97 | { 98 | var dict = new Dictionary 99 | { 100 | ["111"] = new Person { Name = "Roger" } 101 | }; 102 | 103 | if (dict.TryGetValue("111", out var person)) 104 | { 105 | person.Name = "Steve"; 106 | } 107 | } 108 | 109 | private class User 110 | { 111 | public string Ssn { get; set; } 112 | 113 | public string Name { get; set; } 114 | 115 | public override bool Equals(object obj) => obj is User us ? Ssn == us.Ssn : false; 116 | 117 | // Some collections group items to buckets by hashcode 118 | public override int GetHashCode() => Ssn.GetHashCode(); 119 | } 120 | 121 | // For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise. 122 | // For reference types other than string, == returns true if its two operands refer to the same object. 123 | 124 | [Fact] 125 | public void EqualsExample() 126 | { 127 | var person1 = new Person { Name = "James" }; 128 | var person2 = new Person { Name = "James" }; 129 | 130 | Assert.False(person1.Equals(person2)); 131 | Assert.False(person1 == person2); 132 | Assert.True(person1 == person1); 133 | 134 | var user1 = new User { Ssn = "12345" }; 135 | var user2 = new User { Ssn = "12345" }; 136 | 137 | Assert.True(user1.Equals(user2)); 138 | Assert.False(user1 == user2); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /csharp-tutorial/03_Null.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | namespace csharp_tutorial 8 | { 9 | public class Null 10 | { 11 | [Fact] 12 | public void Null_Coalescing() 13 | { 14 | string value = null; 15 | 16 | // Null coalescing, if left side is null, take right side 17 | string print = value ?? "default value"; 18 | 19 | Assert.Equal("default value", print); 20 | 21 | // Same as normal one line condition 22 | string print2 = value != null ? value : "default value"; 23 | 24 | Assert.Equal("default value", print2); 25 | 26 | // Set something to value 27 | value = "This has something"; 28 | 29 | string print3 = value ?? "default value"; 30 | 31 | Assert.Equal("This has something", print3); 32 | } 33 | 34 | [Fact] 35 | public void Null_Coalescing_CompoundAssignment() 36 | { 37 | string value = string.Empty; 38 | 39 | //value = value ?? "default value"; 40 | value ??= "default value"; 41 | } 42 | 43 | public class NullExample 44 | { 45 | public NullExample Child { get; set; } 46 | 47 | public string Text { get; set; } 48 | } 49 | 50 | [Fact] 51 | public void Null_Check() 52 | { 53 | var hello = new NullExample 54 | { 55 | Child = new NullExample { Text = "Abba" } 56 | }; 57 | 58 | string text = hello.Child.Text; 59 | 60 | try 61 | { 62 | // Because of null values NullReferenceException is thrown 63 | string textException = hello.Child.Child.Child.Text; 64 | } 65 | catch (NullReferenceException) 66 | { 67 | } 68 | 69 | string text2 = hello?.Child?.Child?.Child?.Text; 70 | Assert.Null(text2); 71 | 72 | string text3 = hello?.Child?.Text; 73 | Assert.Equal(text, text3); 74 | 75 | // Null coalescing check 76 | var text4 = hello?.Child?.Child?.Text ?? "Not set"; 77 | 78 | Assert.Equal("Not set", text4); 79 | } 80 | 81 | [Fact] 82 | public void Nullable_Types() 83 | { 84 | // int is a value type and values types can't be null (default value of value type is 0bit, so for int is 0, bool is false etc.) 85 | // By adding ? to type identifier value types can also be null 86 | int? myValue = null; 87 | int valueCheck = myValue ?? 2; 88 | 89 | Assert.Equal(2, valueCheck); 90 | 91 | myValue = 6; 92 | 93 | valueCheck = myValue ?? 2; 94 | 95 | Assert.Equal(6, valueCheck); 96 | 97 | if (myValue.HasValue) 98 | { 99 | // Do something 100 | } 101 | } 102 | 103 | #nullable enable 104 | // Normally would just add enable to csproj-file to enable nullable for the whole project 105 | 106 | [Fact] 107 | public void Nullable_ReferenceTypes() 108 | { 109 | var sensor = GetSensor(0); 110 | var data = GetData(sensor); 111 | Assert.Equal(3, data); 112 | } 113 | 114 | public SensorDto GetSensor(int id) 115 | { 116 | if (id == 0) 117 | { 118 | return new SensorDto { Data = 3 }; 119 | } 120 | else 121 | { 122 | throw new Exception("Sensor not found"); 123 | // if return value would be nullable (SensorDto?), then null would be possible 124 | //return null; 125 | } 126 | } 127 | 128 | public double GetData(SensorDto sensor) 129 | { 130 | // No need for null check as sensor can't be null 131 | return sensor.Data; 132 | } 133 | 134 | [Fact] 135 | public void Nullable_CanBeNull() 136 | { 137 | var sensor = GetSensorCanBeNull(0); 138 | 139 | if (sensor == null) 140 | return; 141 | 142 | var data = GetData(sensor); 143 | Assert.Equal(3, data); 144 | } 145 | 146 | public SensorDto? GetSensorCanBeNull(int id) 147 | { 148 | if (id == 0) 149 | { 150 | return new SensorDto(); 151 | } 152 | else 153 | { 154 | return null; 155 | } 156 | } 157 | 158 | public double GetDataCanBeNull(SensorDto? sensor) 159 | { 160 | if (sensor == null) 161 | return double.MinValue; 162 | 163 | return sensor.Data; 164 | } 165 | 166 | // Get GetSensorAsync with nullable reference type 167 | public static async Task GetSensorAsync(string sensrorId = "iddqd") 168 | { 169 | using (var client = new HttpClient()) 170 | { 171 | var response = await client.GetAsync($"http://dummy-sensors.azurewebsites.net/api/sensor/{sensrorId}"); 172 | 173 | if (!response.IsSuccessStatusCode) 174 | { 175 | return null; 176 | //throw new Exception("Sensor not found"); 177 | } 178 | 179 | var sensorJson = await response.Content.ReadAsStringAsync(); 180 | return JsonConvert.DeserializeObject(sensorJson); 181 | } 182 | } 183 | 184 | #nullable disable 185 | } 186 | } -------------------------------------------------------------------------------- /csharp-tutorial/04_ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace csharp_tutorial 6 | { 7 | public class ExensionMethods 8 | { 9 | [Fact] 10 | public void ExtensionMethods_Value() 11 | { 12 | var number = 200; 13 | Assert.True(number.IsPositive()); 14 | } 15 | 16 | [Fact] 17 | public void ExtensionMethods_Class() 18 | { 19 | var person = new Person { FirstName = "Larry", LastName = "Smith" }; 20 | Assert.Equal("Larry Smith", person.FullName()); 21 | } 22 | 23 | [Fact] 24 | public async Task ExtensionMethods_InsteadOfInheritance() 25 | { 26 | var client = new HttpClient(); 27 | var response = await client.OptionsAsync("www.google.com"); 28 | } 29 | } 30 | 31 | public class Person 32 | { 33 | public string FirstName { get; set; } 34 | public string LastName { get; set; } 35 | } 36 | 37 | public static class MyExtensions 38 | { 39 | public static bool IsPositive(this int value) 40 | { 41 | return value > 0; 42 | } 43 | 44 | public static string FullName(this Person person) 45 | { 46 | return person.FirstName + " " + person.LastName; 47 | } 48 | 49 | // NOTE: Original example was with PatchAsync, but nowadays HttpClient includes PatchAsync method 50 | public static async Task OptionsAsync(this HttpClient client, string requestUri) 51 | { 52 | //var request = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri) { Content = content }; 53 | var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), requestUri); 54 | return await client.SendAsync(request); 55 | } 56 | } 57 | 58 | // Without ExtensionMethods we would need to inherit HttpClient 59 | public class MyHttpClient : HttpClient 60 | { 61 | public async Task OptionsAsync(string requestUri) 62 | { 63 | //var request = new HttpRequestMessage(new HttpMethod("PATCH"), requestUri) { Content = content }; 64 | var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), requestUri); 65 | return await SendAsync(request); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /csharp-tutorial/05_Delegates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Xunit; 4 | 5 | namespace csharp_tutorial 6 | { 7 | public class DelegateExamples 8 | { 9 | [Fact] 10 | public void Anonymous_Functions() 11 | { 12 | // These are all same thing 13 | 14 | // Lambda syntax 15 | Func func = (s) => $"Hello {s}"; 16 | Func func2 = (s) => { return $"Hello {s}"; }; 17 | 18 | // Old-school delegate syntax (do not use) 19 | Func func3 = delegate (string s) { return $"Hello {s}"; }; 20 | // In same case had to use delefate where amount of parameters was different 21 | // public delegate T Method(params object[] parameters); 22 | //var funcParams = delegate (params object[] s) { return $"Hello {s}"; }; 23 | 24 | // Or can just take reference to method 25 | Func func4 = GetHelloText; 26 | 27 | // Hopefully some day compiler will get smart enough and we can just do this 28 | //var func = (s) => { return $"Hello {s}"; }; 29 | 30 | Trace.WriteLine(func("World")); 31 | Trace.WriteLine(func2("World")); 32 | Trace.WriteLine(func3("World")); 33 | Trace.WriteLine(func4("World")); 34 | 35 | var sayer = new HelloSayer(func); 36 | string text = sayer.Say("World"); 37 | 38 | var sayer2 = new HelloSayer((s) => $"Not {s}"); 39 | string text2 = sayer2.Say("Nice"); 40 | 41 | Trace.WriteLine(text); 42 | Trace.WriteLine(text2); 43 | } 44 | 45 | public class HelloSayer 46 | { 47 | private readonly Func _say; 48 | 49 | public HelloSayer(Func say) 50 | { 51 | _say = say; 52 | } 53 | 54 | public string Say(string text) 55 | { 56 | return _say(text); 57 | } 58 | } 59 | 60 | private string GetHelloText(string text) 61 | { 62 | return $"Hello {text}"; 63 | } 64 | 65 | [Fact] 66 | public void MulticastDelegates() 67 | { 68 | Action crunchNumber = (i) => { Trace.WriteLine($"Do some fancy stuff with this integer {i}"); }; 69 | 70 | crunchNumber(1); 71 | 72 | // Later we decide that we need to do some writing to log when action is executed 73 | crunchNumber += (i) => Trace.WriteLine($"Writing to log {i}"); 74 | 75 | crunchNumber(2); 76 | 77 | // Later also add POST 78 | crunchNumber += (i) => 79 | { 80 | //TODO: POST with http to some external service 81 | Trace.WriteLine($"POST {i}"); 82 | }; 83 | 84 | crunchNumber(3); 85 | } 86 | 87 | [Fact] 88 | public void Retry_Example() 89 | { 90 | int Add2(int i) => i + 2; 91 | 92 | T MaybeException(Func action) 93 | { 94 | if (DateTime.Now.Ticks % 2 == 0) 95 | throw new Exception("Bad luck"); 96 | return action(); 97 | } 98 | 99 | Assert.Equal(5, Add2(3)); 100 | 101 | var result = RetryHelper(() => MaybeException(() => Add2(4))); 102 | Assert.Equal(6, result); 103 | } 104 | 105 | public static T RetryHelper(Func action, int tryCount = 2) 106 | { 107 | while (true) 108 | { 109 | try 110 | { 111 | return action(); 112 | } 113 | catch (Exception) 114 | { 115 | Trace.WriteLine("Got exception"); 116 | 117 | if (--tryCount > 0) 118 | continue; 119 | 120 | throw; 121 | } 122 | } 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /csharp-tutorial/06_Anonymous.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Dynamic; 6 | using Xunit; 7 | 8 | namespace csharp_tutorial 9 | { 10 | public class AnonymousExamples 11 | { 12 | [Fact] 13 | public void Implicit_Type() 14 | { 15 | int myNumeber = 2; 16 | var otherNumber = 2; 17 | 18 | Assert.Equal(myNumeber, otherNumber); 19 | 20 | string hello = "Hello"; 21 | var hello2 = "Hello"; 22 | 23 | Assert.Equal(hello, hello2); 24 | } 25 | 26 | [Fact] 27 | public void Anonymous_Types() 28 | { 29 | var hello = new { Text = "Hello", SubType = new { Text = "World" } }; 30 | 31 | //String interpolation vs format 32 | Assert.Equal("Hello World", $"{hello.Text} {hello.SubType.Text}"); 33 | Assert.Equal("Hello World", string.Format("{0} {1}", hello.Text, hello.SubType.Text)); 34 | 35 | var helloDynamic = GetHello(); 36 | 37 | //String interpolation 38 | Assert.Equal("Hello World", $"{helloDynamic.Text} {helloDynamic.SubType.Text}"); 39 | } 40 | 41 | // Can return anonymous types with dynamic 42 | public dynamic GetHello() 43 | { 44 | return new { Text = "Hello", SubType = new { Text = "World" } }; 45 | } 46 | 47 | [Fact] 48 | public void Dynamic_Immutable() 49 | { 50 | dynamic immu = new { Name = "Immu" }; 51 | 52 | Assert.Equal(immu.Name, "Immu"); 53 | 54 | // Dynamic objects are read only 55 | // immu.Name = "Not possible"; 56 | 57 | dynamic person = new ExpandoObject(); 58 | person.Name = "James"; 59 | person.Age = 50; 60 | 61 | Assert.Equal(50, person.Age); 62 | 63 | person.Age = 30; 64 | Assert.Equal(30, person.Age); 65 | 66 | dynamic CreateObjectFor(Dictionary values) 67 | { 68 | dynamic personExpando = new ExpandoObject(); 69 | var dictionary = (IDictionary)personExpando; 70 | 71 | foreach (var pair in values) 72 | dictionary.Add(pair.Key, pair.Value); 73 | 74 | return personExpando; 75 | } 76 | 77 | var properties = new Dictionary() 78 | { 79 | ["Name"] = "James", 80 | ["Age"] = 50 81 | }; 82 | 83 | dynamic person2 = CreateObjectFor(properties); 84 | 85 | // Common case is that you get some data, parse do some stuff and create a new object from it and send it 86 | person2.Age = 50; 87 | var json = JsonConvert.SerializeObject(person2); 88 | 89 | // It is also possible to add methods 90 | person2.SayHello = new Action(() => Trace.WriteLine("Hello")); 91 | 92 | var expDict = (IDictionary)person2; 93 | expDict.Add("SayWorld", new Action(() => { Trace.WriteLine("World"); })); 94 | 95 | person2.SayHello(); 96 | person2.SayWorld(); 97 | } 98 | 99 | [Fact] 100 | public void DynamicsCompilerOff_SetWrongType() 101 | { 102 | dynamic validNumber = GetNumber(); 103 | int other = TransformIntToString(validNumber); 104 | } 105 | 106 | private int GetNumber() 107 | { 108 | return 4; 109 | } 110 | 111 | private string TransformIntToString(int input) 112 | { 113 | return input.ToString(); 114 | } 115 | 116 | [Fact] 117 | public void DynamicsCompilerOff_ReturnWrongType() 118 | { 119 | int a = GetNumber2IfParameterTrue(true); 120 | 121 | int b = GetNumber2IfParameterTrue(false); 122 | } 123 | 124 | private int GetNumber2IfParameterTrue(dynamic isTrue) 125 | { 126 | dynamic GetInner(bool check) 127 | { 128 | if (check) 129 | return 2; 130 | else 131 | return "1"; 132 | } 133 | 134 | // When using dynamics, compiler will not give errors on compile time 135 | var value = GetInner(isTrue); 136 | return value; 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /csharp-tutorial/07_Linq.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | namespace csharp_tutorial 7 | { 8 | public class LinqExamples 9 | { 10 | // LINQ was added to standard library before all this was widely used and 11 | // MS decided to use SQL namespace instead of mathematical one 12 | // map is select 13 | // filter is where 14 | // reduce is aggregate 15 | 16 | // LINQ is a good example of extension methods, delegates and anonymous types 17 | 18 | private class Person 19 | { 20 | public string Name { get; set; } 21 | public int Location { get; set; } 22 | public int Salary { get; set; } 23 | } 24 | 25 | [Fact] 26 | public void Linq() 27 | { 28 | var batch1 = new List(10) 29 | { 30 | new Person { Name = "Andy", Location = 1, Salary = 1 }, 31 | new Person { Name = "Thomas", Location = 2, Salary = 1 }, 32 | new Person { Name = "Jefferson", Location = 2, Salary = 2 }, 33 | new Person { Name = "Ben", Location = 1, Salary = 2 } 34 | }; 35 | 36 | var batch2 = new List(10) 37 | { 38 | new Person { Name = "Jack", Location = 1, Salary = 2 }, 39 | new Person { Name = "Daniel", Location = 2, Salary = 2 }, 40 | new Person { Name = "Sam", Location = 2, Salary = 1 }, 41 | new Person { Name = "Dick", Location = 2, Salary = 2 } 42 | }; 43 | 44 | // Merge (combine 2 lists) 45 | var team = batch1.Union(batch2); 46 | 47 | // Filter (select only items that match with predicate) 48 | var location2 = team.Where(x => x.Location == 2); 49 | 50 | // Map (execute selection to every item) 51 | var mapWithNameAndSalary = team.Select(x => new { x.Name, x.Salary }); 52 | var map2 = team.Select(x => x.Location + ":" + x.Name); 53 | 54 | // Reduce (list to single item), Sum, Average 55 | var totalWithAfgg = team 56 | .Select(x => x.Salary) 57 | .Aggregate((acc, curr) => acc + curr); 58 | 59 | var totalExpense = team.Select(x => x.Salary).Sum(); 60 | var totalExpense2 = team.Sum(x => x.Salary); 61 | var average = team.Average(e => e.Salary); 62 | 63 | var highestSalary = batch1.Where(e => e.Salary == batch1.Max(x => x.Salary)); 64 | 65 | // All names to string with aggregate 66 | var allNames = team.Select(x => x.Name).Aggregate((acc, curr) => acc + "," + curr); 67 | 68 | // First, Single 69 | var item = batch1.FirstOrDefault(e => e.Location == 3); 70 | var item2 = batch1.Where(e => e.Location == 2).First(); 71 | var item3 = batch1.Single(e => e.Salary == 3); 72 | 73 | // Group by 74 | var locationSalary = batch1 75 | .GroupBy(e => e.Location) 76 | .Select(e => new { Location = e.Key, TotalSalary = e.Sum(x => x.Salary) }); 77 | 78 | // Zip 79 | var batch1bonus = new int[] { 5, 2, 3, 4 }; 80 | var bonusAdded = batch1.Zip(batch1bonus, (person, bonus) => new { person.Name, Pay = person.Salary + bonus }); 81 | } 82 | 83 | [Fact] 84 | public void Linq_ReferenceTypes() 85 | { 86 | var employees = new List(10) 87 | { 88 | new Person { Name = "Andy", Location = 1, Salary = 1 }, 89 | new Person { Name = "Thomas", Location = 2, Salary = 1 }, 90 | new Person { Name = "Jefferson", Location = 2, Salary = 2 }, 91 | new Person { Name = "Ben", Location = 1, Salary = 2 } 92 | }; 93 | 94 | // Give raise to all in location 2 95 | employees 96 | .Where(x => x.Location == 2) 97 | .ToList() 98 | .ForEach(x => x.Salary += 1); 99 | 100 | // Because person is a reference type, original item's salary changes 101 | 102 | // Some think that ForEach should not be part of LINQ as it is mainly used to mutate data 103 | } 104 | 105 | [Fact] 106 | public void Linq_Lazy() 107 | { 108 | var names = new[] { "Timmy", "Sammy" }; 109 | 110 | // Creteate new Person for Timmy and Sammy 111 | var selected = names.Select(e => new Person { Name = e }); 112 | 113 | // Select Timmy 114 | var timmy = selected.Where(e => e.Name == "Timmy"); 115 | 116 | foreach (var s in selected) 117 | s.Salary = 40; 118 | 119 | var timmysSalary = timmy.SingleOrDefault().Salary; 120 | 121 | Assert.Equal(0, timmysSalary); 122 | } 123 | 124 | [Fact] 125 | public void WordCount() 126 | { 127 | var data = "Deer Bear River\nCar Car River\nDeer Car Bear"; 128 | 129 | var split = data.Split('\n'); 130 | 131 | var map = split.Select(s => s.Split(' ').Select(i => new { Title = i, Value = 1 }).ToList()); 132 | var map2 = split.Select(s => s.Split(' ').Select(i => new { Title = i, Value = 1 })); 133 | var map3 = split.Select(s => s.Split(' ').ToList()); 134 | 135 | var flat = map3.SelectMany(i => i); 136 | var groupedByWords = flat.GroupBy(i => i); 137 | var red = groupedByWords.Select(i => new { Title = i.Key, Count = i.Count() }); 138 | 139 | var shuffle = map.SelectMany(i => i).GroupBy(i => i.Title); 140 | 141 | var reduce = shuffle.Select(i => new { Title = i.Key, Count = i.Count() }); 142 | } 143 | 144 | private class Requester 145 | { 146 | public string Ssn { get; set; } 147 | public string Name { get; set; } 148 | } 149 | 150 | [Fact] 151 | public void K() 152 | { 153 | // Combine requests to sets that have same requesters 154 | 155 | var requests = new Dictionary>(); 156 | 157 | requests.Add("A", new List { 158 | new Requester { Ssn = "1" }, 159 | new Requester { Ssn = "2" }, 160 | new Requester { Ssn = "3" } 161 | }); 162 | 163 | requests.Add("B", new List { 164 | new Requester { Ssn = "2" }, 165 | new Requester { Ssn = "1" }, 166 | new Requester { Ssn = "3" } 167 | }); 168 | 169 | requests.Add("C", new List { 170 | new Requester { Ssn = "3" } 171 | }); 172 | 173 | requests.Add("D", new List { 174 | new Requester { Ssn = "3" } 175 | }); 176 | 177 | requests.Add("E", new List { 178 | new Requester { Ssn = "1" }, 179 | new Requester { Ssn = "2" } 180 | }); 181 | 182 | IEnumerable, IList>> requestSet; 183 | 184 | requestSet = requests.Select(s => new 185 | { 186 | RequestId = s.Key, 187 | SsnList = s.Value.OrderBy(r => r.Ssn).Select(r => r.Ssn).Aggregate((prev, curr) => prev + "," + curr) 188 | }) 189 | .GroupBy(s => s.SsnList) 190 | .Select(s => Tuple.Create(s.Select(r => r.RequestId).ToList(), requests[s.First().RequestId])); 191 | } 192 | 193 | [Fact] 194 | public void Compare() 195 | { 196 | // All thata can also be implemented with using object's Equals method 197 | 198 | var requests = new Dictionary>(); 199 | 200 | requests.Add("A", new List { 201 | new Requester2 { Ssn = "1" }, 202 | new Requester2 { Ssn = "2" }, 203 | new Requester2 { Ssn = "3" } 204 | }); 205 | 206 | requests.Add("B", new List { 207 | new Requester2 { Ssn = "2" }, 208 | new Requester2 { Ssn = "1" }, 209 | new Requester2 { Ssn = "3" } 210 | }); 211 | 212 | requests.Add("C", new List { 213 | new Requester2 { Ssn = "3" } 214 | }); 215 | 216 | requests.Add("D", new List { 217 | new Requester2 { Ssn = "3" } 218 | }); 219 | 220 | requests.Add("E", new List { 221 | new Requester2 { Ssn = "1" }, 222 | new Requester2 { Ssn = "2" } 223 | }); 224 | 225 | // B contains all from A (order doesn't matter) 226 | var hasAll = requests["A"].All(requests["B"].Contains); 227 | 228 | Assert.True(hasAll); 229 | 230 | var results = requests["A"].Contains(requests["B"].First(e => e.Ssn == "2")); 231 | Assert.False(results); 232 | 233 | results = requests["A"].Contains(requests["A"].First(e => e.Ssn == "2")); 234 | Assert.False(results); 235 | 236 | results = requests["A"].Contains(new Requester2 { Ssn = "2" }); 237 | Assert.False(results); 238 | } 239 | 240 | private class Requester2 241 | { 242 | public string Ssn { get; set; } 243 | 244 | public string Name { get; set; } 245 | 246 | public override bool Equals(object obj) => obj is Requester2 ? Ssn == ((Requester2)obj).Ssn : false; 247 | 248 | // Dictionaries and Hashsets use hash code to compare elements 249 | public override int GetHashCode() => Ssn.GetHashCode(); 250 | } 251 | } 252 | } -------------------------------------------------------------------------------- /csharp-tutorial/08_Composition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace csharp_tutorial 5 | { 6 | public class CompositionExamples 7 | { 8 | // Compare inheritance / strategy pattern / composition 9 | // In real life inheritance/composition cases are never this simple 10 | 11 | public class Account 12 | { 13 | public double Balance { get; set; } 14 | } 15 | 16 | public class Transaction 17 | { 18 | public string Target { get; set; } 19 | public Account Account { get; set; } 20 | } 21 | 22 | public abstract class Handler 23 | { 24 | public abstract bool Handle(Transaction transaction); 25 | } 26 | 27 | public class AllowRichHandler : Handler 28 | { 29 | public override bool Handle(Transaction transaction) 30 | { 31 | return transaction.Account.Balance > 100; 32 | } 33 | } 34 | 35 | public class AllowAllHandler : Handler 36 | { 37 | public override bool Handle(Transaction transaction) 38 | { 39 | return transaction.Account.Balance > 0; 40 | } 41 | } 42 | 43 | [Fact] 44 | public void Inheritance() 45 | { 46 | Handler suitabilityFactory(int customerType) 47 | { 48 | if (customerType == 0) 49 | return new AllowAllHandler(); 50 | else 51 | return new AllowRichHandler(); 52 | } 53 | 54 | var transactionput = new Transaction 55 | { 56 | Target = "GOOGL", 57 | Account = new Account { Balance = 50 } 58 | }; 59 | 60 | Handler h1 = suitabilityFactory(0); 61 | var response = h1.Handle(transactionput); 62 | Assert.True(response); 63 | 64 | Handler h2 = suitabilityFactory(1); 65 | var response2 = h2.Handle(transactionput); 66 | Assert.False(response2); 67 | } 68 | 69 | public interface ISuitabilityStrategy 70 | { 71 | bool Handle(Transaction transaction); 72 | } 73 | 74 | public class AllowAllStrategy : ISuitabilityStrategy 75 | { 76 | public bool Handle(Transaction transaction) => transaction.Account.Balance > 0; 77 | } 78 | 79 | public class AllowOver100Strategy : ISuitabilityStrategy 80 | { 81 | public bool Handle(Transaction transaction) => transaction.Account.Balance > 100; 82 | } 83 | 84 | public class HandlerStrategy 85 | { 86 | public ISuitabilityStrategy SuitabilityStrategy { get; set; } 87 | 88 | public bool Handle(Transaction transaction) 89 | { 90 | return SuitabilityStrategy.Handle(transaction); 91 | } 92 | } 93 | 94 | [Fact] 95 | public void Strategy() 96 | { 97 | var handler = new HandlerStrategy(); 98 | 99 | ISuitabilityStrategy getStrategy(int strategyType) 100 | { 101 | return strategyType == 0 ? new AllowAllStrategy() as ISuitabilityStrategy : new AllowOver100Strategy(); 102 | } 103 | 104 | var transaction = new Transaction 105 | { 106 | Target = "GOOGL", 107 | Account = new Account { Balance = 50 } 108 | }; 109 | 110 | handler.SuitabilityStrategy = getStrategy(0); 111 | 112 | var response = handler.Handle(transaction); 113 | Assert.True(response); 114 | 115 | handler.SuitabilityStrategy = getStrategy(1); 116 | var response2 = handler.Handle(transaction); 117 | Assert.False(response2); 118 | } 119 | 120 | public class HandlerComposition 121 | { 122 | private readonly Func _handleFunc; 123 | 124 | public HandlerComposition(Func handleFunc) => _handleFunc = handleFunc; 125 | 126 | public bool Handle(Transaction transaction) => _handleFunc(transaction); 127 | } 128 | 129 | [Fact] 130 | public void Composition() 131 | { 132 | HandlerComposition Builder(int type) 133 | { 134 | if (type == 0) 135 | return new HandlerComposition((i) => i.Account.Balance > 0); 136 | else 137 | return new HandlerComposition((i) => i.Account.Balance > 100); 138 | } 139 | 140 | var transaction = new Transaction 141 | { 142 | Target = "GOOGL", 143 | Account = new Account { Balance = 50 } 144 | }; 145 | 146 | HandlerComposition h1 = Builder(0); 147 | var response = h1.Handle(transaction); 148 | Assert.True(response); 149 | 150 | HandlerComposition h2 = Builder(1); 151 | var response2 = h2.Handle(transaction); 152 | Assert.False(response2); 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /csharp-tutorial/09_Tasks.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Diagnostics; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace csharp_tutorial 10 | { 11 | public class TaskExamples 12 | { 13 | [Fact] 14 | public void RunThread() 15 | { 16 | void BackgroundExecution() 17 | { 18 | while (true) 19 | { 20 | // Do something important 21 | var data = SensorData.GetSensorSync(); 22 | Thread.Sleep(1000); 23 | Trace.WriteLine($"In the background: {data.Data}"); 24 | } 25 | } 26 | 27 | var t = new Thread(new ThreadStart(BackgroundExecution)); 28 | t.Start(); 29 | 30 | while (t.IsAlive) 31 | Thread.Sleep(1000); 32 | } 33 | 34 | [Fact] 35 | public void RunTask() 36 | { 37 | void BackgroundExecution() 38 | { 39 | while (true) 40 | { 41 | var data = SensorData.GetSensorSync(); 42 | Thread.Sleep(1000); 43 | Trace.WriteLine($"In the background: {data.Data}"); 44 | } 45 | } 46 | 47 | var task = Task.Run(() => BackgroundExecution()); 48 | 49 | while (task.IsCompleted == false) 50 | Thread.Sleep(1000); 51 | } 52 | 53 | [Fact] 54 | public void RunTaskReturn() 55 | { 56 | // Often tasks return something rather than just run "forever" on the background 57 | 58 | double BackgroundExecution() 59 | { 60 | var data = SensorData.GetSensorSync(); 61 | Thread.Sleep(1000); 62 | return data.Data; 63 | } 64 | 65 | var task = Task.Run(() => BackgroundExecution()); 66 | 67 | while (task.IsCompleted == false) 68 | { 69 | Thread.Sleep(1000); 70 | } 71 | 72 | var result = task.Result; 73 | 74 | Assert.Equal(2, result); 75 | } 76 | 77 | [Fact] 78 | public async Task RunAtBackground() 79 | { 80 | var cts = new CancellationTokenSource(); 81 | var token = cts.Token; 82 | 83 | var collection = new BlockingCollection(); 84 | 85 | // Explain why task has token and why it is checked in while loop 86 | var backgroundTask = Task.Run(() => 87 | { 88 | while (token.IsCancellationRequested == false) 89 | { 90 | var sensorToFetch = collection.Take(token); 91 | var sensorData = SensorData.GetSensorSync(sensorToFetch); 92 | 93 | // Do something nice with the result 94 | Trace.WriteLine(JsonConvert.SerializeObject(sensorData)); 95 | } 96 | }, token); 97 | 98 | // Application keeps getting requests from somewhere 99 | while (true) 100 | { 101 | await Task.Delay(5000); 102 | collection.Add(DateTime.Now.Ticks % 2 == 0 ? "abba5" : "iddqd"); 103 | } 104 | } 105 | 106 | private Task GetSensorDataAsync(string id) 107 | { 108 | return Task.Run(() => 109 | { 110 | return SensorData.GetDataSync(id); 111 | }); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /csharp-tutorial/10_Async.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace csharp_tutorial 9 | { 10 | public class AsyncExamples 11 | { 12 | [Fact] 13 | public async Task TaskAndResult() 14 | { 15 | var task = SlowAsyncAction1Sec(1, 5000); 16 | 17 | var firstResult = task.Result; 18 | 19 | var result = await SlowAsyncAction1Sec(1, 5000); 20 | 21 | Assert.Equal(task.Result, result); 22 | } 23 | 24 | [Fact] 25 | public async Task AsyncAwait() 26 | { 27 | var sw = Stopwatch.StartNew(); 28 | 29 | var r1 = await SlowAsyncAction1Sec(1); 30 | var r2 = await SlowAsyncAction1Sec(2); 31 | var r3 = await SlowAsyncAction1Sec(3); 32 | var r4 = await SlowAsyncAction1Sec(4); 33 | 34 | Trace.WriteLine($"{sw.ElapsedMilliseconds}ms"); 35 | 36 | sw = Stopwatch.StartNew(); 37 | 38 | var t1 = SlowAsyncAction1Sec(1); 39 | var t2 = SlowAsyncAction1Sec(2); 40 | var t3 = SlowAsyncAction1Sec(3); 41 | var t4 = SlowAsyncAction1Sec(4); 42 | 43 | await Task.WhenAll(t1, t2, t3, t4); 44 | 45 | Trace.WriteLine($"{sw.ElapsedMilliseconds}ms"); 46 | 47 | // To get results need to use .Result 48 | var result1 = t1.Result; 49 | } 50 | 51 | private Task SlowAsyncAction1Sec(int id, int sleepTime = 1000) 52 | { 53 | return Task.Run(() => 54 | { 55 | Thread.Sleep(sleepTime); 56 | Trace.WriteLine($"Ready: {id}"); 57 | return id * 2; 58 | }); 59 | } 60 | 61 | [Fact] 62 | public async Task AsyncAwait_Comparison() 63 | { 64 | // Warmup 65 | SensorData.GetDataSync("iddqd"); 66 | await SensorData.GetDataAsync("iddqd"); 67 | 68 | var sw = Stopwatch.StartNew(); 69 | 70 | var syncResult1 = SensorData.GetDataSync("iddqd"); 71 | var syncResult2 = SensorData.GetDataSync("idkfa"); 72 | var syncResult3 = SensorData.GetDataSync("abba5"); 73 | var syncResult4 = SensorData.GetDataSync("acdc1"); 74 | 75 | var syncTime = sw.ElapsedMilliseconds; 76 | sw = Stopwatch.StartNew(); 77 | 78 | var r1 = await SensorData.GetDataAsync("iddqd"); 79 | var r2 = await SensorData.GetDataAsync("idkfa"); 80 | var r3 = await SensorData.GetDataAsync("abba5"); 81 | var r4 = await SensorData.GetDataAsync("acdc1"); 82 | 83 | var asyncTime = sw.ElapsedMilliseconds; 84 | sw = Stopwatch.StartNew(); 85 | 86 | var t1 = SensorData.GetDataAsync("iddqd"); 87 | var t2 = SensorData.GetDataAsync("idkfa"); 88 | var t3 = SensorData.GetDataAsync("abba5"); 89 | var t4 = SensorData.GetDataAsync("acdc1"); 90 | 91 | var results = await Task.WhenAll(t1, t2, t3, t4); 92 | 93 | var awaitTime = sw.ElapsedMilliseconds; 94 | } 95 | 96 | [Fact] 97 | public async Task Tasks_FromSync() 98 | { 99 | var ids = new string[] { "iddqd", "idkfq", "abba5", "acdc1" }; 100 | 101 | var tasks = new List>(); 102 | 103 | foreach (var id in ids) 104 | { 105 | var task = Task.Run(() => 106 | { 107 | var value = SensorData.GetSensorSync(id); 108 | return value; 109 | }); 110 | 111 | // var task = Task.Run(() => SensorData.GetSensorSync(id)); 112 | 113 | tasks.Add(task); 114 | } 115 | 116 | var results = await Task.WhenAll(tasks); 117 | } 118 | 119 | [Fact] 120 | public async Task Tasks_FromSync_Linq() 121 | { 122 | var ids = new string[] { "iddqd", "idkfq", "abba5", "acdc1" }; 123 | 124 | var tasks = ids.Select(i => Task.Run(() => SensorData.GetSensorSync(i))).ToList(); 125 | 126 | var results = await Task.WhenAll(tasks); 127 | } 128 | 129 | [Fact] 130 | public async Task Tasks_FromAsync() 131 | { 132 | var ids = new string[] { "iddqd", "idkfq", "abba5", "acdc1" }; 133 | 134 | var tasks = new List>(); 135 | 136 | foreach (var id in ids) 137 | { 138 | var dataTask = SensorData.GetDataAsync(id); 139 | tasks.Add(dataTask); 140 | } 141 | 142 | var results = await Task.WhenAll(tasks); 143 | } 144 | 145 | [Fact] 146 | public async Task Tasks_FromAsync_Linq() 147 | { 148 | var ids = new string[] { "iddqd", "idkfq", "abba5", "acdc1" }; 149 | 150 | var tasksLinq = ids.Select(e => SensorData.GetSensorAsync(e)).ToList(); 151 | 152 | var results = await Task.WhenAll(tasksLinq); 153 | 154 | // Select is lazy, but now tasksLinq is a List 155 | // Try what happes without .ToList() and with it 156 | var over10Sensors = tasksLinq.Where(e => e.Result.Data > 10).ToList(); 157 | } 158 | 159 | // Sometimes is is really hard to understand how Framework behaves 160 | // Examples have blocking Sleep before and after await Delay 161 | [Fact] 162 | public async Task Threading_Is_Hard() 163 | { 164 | var partiallyBlocking = LongRunningWithSync_Blocking(); 165 | 166 | await partiallyBlocking; 167 | 168 | var returnImmediately = LongRunningWithSync_Non_Blocking(); 169 | 170 | await returnImmediately; 171 | 172 | var returnImmediately2 = LongRunningWithSync_Extra_Thread(); 173 | 174 | await returnImmediately2; 175 | } 176 | 177 | private async Task LongRunningWithSync_Blocking() 178 | { 179 | var delayedTasks = Enumerable.Range(0, 5).Select(async e => 180 | { 181 | Thread.Sleep(e * 1000); 182 | await Task.Delay(e * 1000); 183 | return e; 184 | }); 185 | 186 | await Task.WhenAll(delayedTasks); 187 | } 188 | 189 | private async Task LongRunningWithSync_Non_Blocking() 190 | { 191 | var delayedTasks = Enumerable.Range(0, 5).Select(async e => 192 | { 193 | await Task.Delay(e * 1000); 194 | Thread.Sleep(e * 1000); 195 | return e; 196 | }); 197 | 198 | await Task.WhenAll(delayedTasks); 199 | } 200 | 201 | private async Task LongRunningWithSync_Extra_Thread() 202 | { 203 | var delayedTasks = Enumerable.Range(0, 5).Select(async e => 204 | { 205 | return await Task.Run(async () => 206 | { 207 | Thread.Sleep(e * 1000); 208 | await Task.Delay(e * 1000); 209 | return e; 210 | }); 211 | }); 212 | 213 | await Task.WhenAll(delayedTasks); 214 | } 215 | } 216 | } -------------------------------------------------------------------------------- /csharp-tutorial/11_Closures.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace csharp_tutorial 6 | { 7 | public class Closures 8 | { 9 | [Fact] 10 | public void Simple() 11 | { 12 | int myValue = 4; 13 | 14 | var adder = new Func(i => 15 | { 16 | return myValue + i; 17 | }); 18 | 19 | var result = adder(5); 20 | Assert.Equal(9, result); 21 | 22 | //DoCalcluation(adder, 9); 23 | //Assert.Equal(9, result); 24 | 25 | // myValue will change inside closure 26 | myValue = 6; 27 | 28 | var result2 = adder(5); 29 | Assert.Equal(11, result2); 30 | 31 | //DoCalcluation(adder, 11); 32 | //Assert.Equal(11, result2); 33 | } 34 | 35 | private void DoCalcluation(Func adder, int expected) 36 | { 37 | var result = adder(5); 38 | Assert.Equal(expected, result); 39 | } 40 | 41 | [Fact] 42 | public void SensorId() 43 | { 44 | var selectedSensor = "iddqd"; 45 | 46 | var getData = new Func>(async () => 47 | { 48 | await Task.Delay(1000); 49 | return await SensorData.GetSensorAsync(selectedSensor); 50 | }); 51 | 52 | var sensorTask = getData(); 53 | 54 | selectedSensor = "idkfa"; 55 | 56 | var result = sensorTask.Result; 57 | 58 | // Fix 59 | // var getData = new Func>(async sensorId => 60 | } 61 | 62 | public class SensorRequest 63 | { 64 | public string Id { get; set; } 65 | } 66 | 67 | [Fact] 68 | public void ReferenceType() 69 | { 70 | var sensorRequest = new SensorRequest { Id = "iddqd" }; 71 | 72 | var getData = new Func>(async request => 73 | { 74 | await Task.Delay(1000); 75 | return await SensorData.GetSensorAsync(request.Id); 76 | }); 77 | 78 | var sensorTask = getData(sensorRequest); 79 | 80 | sensorRequest.Id = "idkfa"; 81 | 82 | var result = sensorTask.Result; 83 | } 84 | 85 | [Fact] 86 | public void ReferenceTypeFixed() 87 | { 88 | var sensorRequest = new SensorRequest { Id = "iddqd" }; 89 | 90 | var getData = new Func>(async request => 91 | { 92 | var req = new SensorRequest { Id = request.Id }; 93 | await Task.Delay(1000); 94 | return await SensorData.GetSensorAsync(req.Id); 95 | }); 96 | 97 | var sensorTask = getData(sensorRequest); 98 | 99 | sensorRequest.Id = "idkfa"; 100 | 101 | var result = sensorTask.Result; 102 | } 103 | 104 | public class InputHandlerOptions 105 | { 106 | public int MaxValue = 0; 107 | public bool MustBeEven = false; 108 | public Action OnSuccess; 109 | public Action OnFail; 110 | } 111 | 112 | public class InputHandler 113 | { 114 | private readonly InputHandlerOptions _opts = new InputHandlerOptions(); 115 | 116 | public void AddOptions(Action add) => add(_opts); 117 | 118 | public void Handle(int input) 119 | { 120 | if (input > _opts.MaxValue || (_opts.MustBeEven && input % 2 != 0)) 121 | _opts.OnFail(); 122 | 123 | _opts.OnSuccess(); 124 | } 125 | } 126 | 127 | [Fact] 128 | public void InputHandlerExample() 129 | { 130 | var myMax = 10; 131 | var mustBeEven = false; 132 | var isSuccess = false; 133 | 134 | var handler = new InputHandler(); 135 | 136 | handler.AddOptions((opts) => 137 | { 138 | opts.MaxValue = myMax; 139 | opts.MustBeEven = mustBeEven; 140 | opts.OnSuccess = new Action(() => 141 | { 142 | isSuccess = true; 143 | }); 144 | opts.OnFail = new Action(() => 145 | { 146 | isSuccess = false; 147 | }); 148 | }); 149 | 150 | handler.Handle(9); 151 | Assert.True(isSuccess); 152 | 153 | handler.Handle(11); 154 | Assert.False(isSuccess); 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /csharp-tutorial/12_Event.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | namespace csharp_tutorial 8 | { 9 | public class EventExamples 10 | { 11 | // Events are not much used in web applications, but are real handy in real time systems 12 | 13 | [Fact] 14 | public void Hello_Event() 15 | { 16 | var hello = new HelloHandler(); 17 | var vp = new ValueProcessor(hello); 18 | 19 | // Listen to ValueChanged Event with normal and anonymous function 20 | hello.ValueChanged += (s, e) => { Trace.WriteLine($"From anonymous: New value is {e}"); }; 21 | hello.ValueChanged += Hello_ValueChanged; 22 | 23 | // Difference with Event and multicastdelegate is that although it's public, only owner can set it to null 24 | // hello.ValueChanged = null; 25 | 26 | // Running for 5 seconds 27 | var sw = Stopwatch.StartNew(); 28 | while (sw.ElapsedMilliseconds < 5000) { } 29 | 30 | vp.Stop(); 31 | 32 | // Problem with anonymous functions is that you can't remove those 33 | hello.ValueChanged -= Hello_ValueChanged; 34 | hello.CurrentValue = 8; // From anonymous function still prints 35 | } 36 | 37 | private void Hello_ValueChanged(object sender, int e) 38 | { 39 | Trace.WriteLine($"New value is {e}"); 40 | } 41 | 42 | /// 43 | /// When CurrentValue is changed will invoke ValueChanged event 44 | /// 45 | public class HelloHandler 46 | { 47 | public event EventHandler ValueChanged; 48 | 49 | private int _myValue; 50 | 51 | public int CurrentValue 52 | { 53 | get { return _myValue; } 54 | set 55 | { 56 | if (_myValue != value) 57 | { 58 | _myValue = value; 59 | ValueChanged?.Invoke(this, _myValue); 60 | } 61 | } 62 | } 63 | } 64 | 65 | /// 66 | /// Updates HelloHandler value periodically 67 | /// 68 | public class ValueProcessor 69 | { 70 | private HelloHandler _helloHandler; 71 | private CancellationTokenSource _cts = new CancellationTokenSource(); 72 | 73 | public ValueProcessor(HelloHandler helloHandler) 74 | { 75 | _helloHandler = helloHandler; 76 | Task.Factory.StartNew(UpdateValue, _cts.Token); 77 | } 78 | 79 | public void Stop() 80 | { 81 | _cts.Cancel(); 82 | } 83 | 84 | private void UpdateValue(object ct) 85 | { 86 | var token = (CancellationToken)ct; 87 | 88 | while (token.IsCancellationRequested == false) 89 | { 90 | var rand = new Random(DateTime.Now.Millisecond); 91 | _helloHandler.CurrentValue = rand.Next(0, 100); 92 | Thread.Sleep(100); 93 | } 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /csharp-tutorial/13_Parallel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace csharp_tutorial 10 | { 11 | public class ParallelExamples 12 | { 13 | [Fact] 14 | public void Parallel_ForEach() 15 | { 16 | // Easy parallel with Parallel.ForEach 17 | // Can't use async with Parallel 18 | 19 | var idList = new List { 1, 2, 3, 4 }; 20 | 21 | var sw = Stopwatch.StartNew(); 22 | 23 | Parallel.ForEach(idList, SlowAction1Sec); 24 | 25 | // Previous statement is same as these 26 | 27 | //Parallel.ForEach(idList, (id) => { SlowAction(id); }); 28 | 29 | //foreach (var id in idList.AsParallel()) 30 | //{ 31 | // SlowAction1Sec(id); 32 | //} 33 | 34 | Trace.WriteLine($"{sw.ElapsedMilliseconds}ms"); 35 | 36 | sw = Stopwatch.StartNew(); 37 | 38 | idList.ForEach(SlowAction1Sec); 39 | 40 | // Previous statement is same as this 41 | //foreach (var id in idList) 42 | //{ 43 | // SlowAction1Sec(id); 44 | //} 45 | 46 | Trace.WriteLine($"{sw.ElapsedMilliseconds}ms"); 47 | } 48 | 49 | [Fact] 50 | public async Task AsyncAwaitParallel() 51 | { 52 | var idList = Enumerable.Range(1, 5); 53 | 54 | Trace.WriteLine($"Parallel.ForEach with async"); 55 | var sw = Stopwatch.StartNew(); 56 | 57 | // No way to know when this is ready, so don't use async with Parallel.ForEach 58 | var res = Parallel.ForEach(idList, async (id) => 59 | { 60 | var result = await SlowAsyncAction1Sec(id); 61 | Trace.WriteLine("Result: " + result); 62 | }); 63 | 64 | await Task.Delay(3); 65 | 66 | Trace.WriteLine($"Parallel.ForEach with async: {sw.ElapsedMilliseconds}ms"); 67 | 68 | // NEXT 69 | 70 | Trace.WriteLine($"AsParallel().ForAll with async"); 71 | sw = Stopwatch.StartNew(); 72 | 73 | // No way to know when this is ready, so don't use async with Parallel.ForEach 74 | idList.AsParallel().ForAll(async (id) => 75 | { 76 | var result = await SlowAsyncAction1Sec(id); 77 | Trace.WriteLine("Result: " + result); 78 | }); 79 | 80 | await Task.Delay(3); 81 | 82 | Trace.WriteLine($"AsParallel().ForAll with async: {sw.ElapsedMilliseconds}ms"); 83 | 84 | /// NEXT 85 | 86 | Trace.WriteLine($"AsParallel Select sync"); 87 | sw = Stopwatch.StartNew(); 88 | 89 | var syncActions = idList.AsParallel().Select(e => SlowAction1SecResult(e)).ToList(); 90 | 91 | syncActions.ForEach(e => Trace.WriteLine("Result: " + e)); 92 | 93 | Trace.WriteLine($"AsParallel Select sync: {sw.ElapsedMilliseconds}ms"); 94 | 95 | /// NEXT 96 | 97 | // This is not much faster, beacause AsParallel already runs these in parallel 98 | Trace.WriteLine($"AsParallel Select async"); 99 | sw = Stopwatch.StartNew(); 100 | 101 | var asyncActions = idList.AsParallel().Select(async e => await SlowAsyncAction1Sec(e)).ToList(); 102 | 103 | asyncActions.ForEach(e => Trace.WriteLine("Result: " + e.Result)); 104 | 105 | Trace.WriteLine($"AsParallel Select async: {sw.ElapsedMilliseconds}ms"); 106 | 107 | /// NEXT 108 | 109 | // This wont be parallel as this is worng: foreach (int i in idList.AsParallel()) 110 | Trace.WriteLine($"Foreach AsParallel no async"); 111 | sw = Stopwatch.StartNew(); 112 | 113 | foreach (int i in idList.AsParallel()) 114 | { 115 | var r = SlowAction1SecResult(i); 116 | Trace.WriteLine($"id: {i} result:{r}"); 117 | } 118 | 119 | Trace.WriteLine($"Foreach AsParallel no async: {sw.ElapsedMilliseconds}ms"); 120 | 121 | /// NEXT 122 | 123 | // This wont be parallel as this is worng: foreach (int i in idList.AsParallel()) 124 | Trace.WriteLine($"Foreach AsParallel with async"); 125 | sw = Stopwatch.StartNew(); 126 | 127 | foreach (int i in idList.AsParallel()) 128 | { 129 | var r = await SlowAsyncAction1Sec(i); 130 | Trace.WriteLine($"id: {i} result:{r}"); 131 | } 132 | 133 | Trace.WriteLine($"Foreach AsParallel with async: {sw.ElapsedMilliseconds}ms"); 134 | } 135 | 136 | [Fact] 137 | public async Task AsyncAwaitForEachSelect() 138 | { 139 | var idList = Enumerable.Range(1, 5); 140 | 141 | // This will work 142 | Trace.WriteLine($"Foreach TaskWhenAll with async"); 143 | var sw = Stopwatch.StartNew(); 144 | 145 | var results = new Dictionary(5); 146 | 147 | foreach (int i in idList) 148 | { 149 | var r = SlowAsyncAction1Sec(i); 150 | results.Add(i, r); 151 | } 152 | 153 | await Task.WhenAll(results.Values.ToList()); 154 | 155 | Trace.WriteLine($"{sw.ElapsedMilliseconds}ms"); 156 | 157 | /// NEXT 158 | 159 | Trace.WriteLine($"Select TaskWhenAll no async"); 160 | 161 | var slowTasks = idList.Select(i => SlowAsyncAction1Sec(i)); 162 | // Tasks finish at different times, but returned array has results in the same order as slowTasks 163 | var slowResuls = await Task.WhenAll(slowTasks); 164 | 165 | /// NEXT 166 | 167 | Trace.WriteLine($"Select TaskWhenAll async"); 168 | sw = Stopwatch.StartNew(); 169 | 170 | var asyncActions2 = idList.Select(async e => await SlowAsyncAction1Sec(e)).ToList(); 171 | 172 | await Task.WhenAll(asyncActions2); 173 | 174 | asyncActions2.ForEach(e => Trace.WriteLine("Result: " + e.Result)); 175 | 176 | Trace.WriteLine($"Select async: {sw.ElapsedMilliseconds}ms"); 177 | } 178 | 179 | private void SlowAction1Sec(int id) 180 | { 181 | // This simulates longer process 182 | Thread.Sleep(1000); 183 | 184 | Trace.WriteLine($"Ready: {id}"); 185 | } 186 | 187 | private int SlowAction1SecResult(int id) 188 | { 189 | // This simulates longer process 190 | Thread.Sleep(1000); 191 | 192 | Trace.WriteLine($"Ready: {id}"); 193 | 194 | return id * 2; 195 | } 196 | 197 | private Task SlowAsyncAction1Sec(int id) 198 | { 199 | return Task.Run(() => 200 | { 201 | Thread.Sleep(1000); 202 | Trace.WriteLine($"Ready: {id}"); 203 | return id * 2; 204 | }); 205 | } 206 | 207 | [Fact] 208 | public void Tasks() 209 | { 210 | // var longProcess = Task.Factory.StartNew(() => { Thread.Sleep(1000); }); 211 | // Task.Run is a shorthand for Task.Factory.StartNew with some default argumnents 212 | var longProcess = Task.Run(() => { Thread.Sleep(1000); }); 213 | 214 | var results = ParallelPartitionerPi(20000000); 215 | } 216 | 217 | // http://stackoverflow.com/a/4283808/1292530 218 | private static decimal ParallelPartitionerPi(int steps) 219 | { 220 | decimal sum = 0.0M; 221 | decimal step = 1.0M / (decimal)steps; 222 | object lockObj = new object(); 223 | 224 | Parallel.ForEach( 225 | Partitioner.Create(0, steps), 226 | () => 0.0M, 227 | (range, state, partial) => 228 | { 229 | for (int i = range.Item1; i < range.Item2; i++) 230 | { 231 | decimal x = (i - 0.5M) * step; 232 | partial += 4.0M / (1.0M + x * x); 233 | } 234 | 235 | return partial; 236 | }, 237 | partial => { lock (lockObj) sum += partial; }); 238 | 239 | return step * sum; 240 | } 241 | } 242 | } -------------------------------------------------------------------------------- /csharp-tutorial/14_Generators.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace csharp_tutorial 7 | { 8 | public class Generators 9 | { 10 | private IEnumerable GetNumbers() 11 | { 12 | yield return 1; 13 | yield return 2; 14 | yield return 3; 15 | yield return 4; 16 | yield return 5; 17 | 18 | foreach (var number in _numbers) 19 | yield return number; 20 | } 21 | 22 | private IEnumerable _numbers = new[] { 6, 7, 8, 9, 10 }; 23 | 24 | [Fact] 25 | public void Simple_Yield() 26 | { 27 | foreach (var result in GetNumbers()) 28 | { 29 | Trace.WriteLine(result); 30 | } 31 | } 32 | 33 | public class WebRequester 34 | { 35 | public IEnumerable GetValues() 36 | { 37 | yield return -1; 38 | 39 | while (true) 40 | { 41 | yield return SensorData.GetDataSync("iddqd"); 42 | yield return SensorData.GetDataSync("idkfa"); 43 | } 44 | } 45 | } 46 | 47 | [Fact] 48 | public void Generator_Sync() 49 | { 50 | var hello = new WebRequester(); 51 | 52 | foreach (var result in hello.GetValues()) 53 | { 54 | Trace.WriteLine(result); 55 | 56 | if (result > 0) 57 | break; 58 | } 59 | } 60 | 61 | public class WebRequesterAsync 62 | { 63 | public IEnumerable> GetValues() 64 | { 65 | while (true) 66 | { 67 | yield return Task.FromResult(-1d); 68 | yield return SensorData.GetDataAsync("iddqd"); 69 | yield return SensorData.GetDataAsync("idkfa"); 70 | } 71 | } 72 | } 73 | 74 | [Fact] 75 | public async Task Generator_Async() 76 | { 77 | var hello = new WebRequesterAsync(); 78 | 79 | foreach (var result in hello.GetValues()) 80 | { 81 | Trace.WriteLine(await result); 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /csharp-tutorial/15_Lazy.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace csharp_tutorial 10 | { 11 | public class Lazy 12 | { 13 | /* 14 | * Do not do expensive operations if not needed 15 | * - DB reads, API fetches 16 | * Do not consumer memory if not needed 17 | * - Create new temporary collections 18 | * - Fetch unnecessary data from database 19 | * ... 20 | * 21 | */ 22 | 23 | public class WebRequesterAsync 24 | { 25 | public IEnumerable> OpenStream(IEnumerable ids) 26 | { 27 | while (true) 28 | { 29 | foreach (var id in ids) 30 | { 31 | yield return SensorData.GetSensorAsync(id); 32 | } 33 | } 34 | } 35 | } 36 | 37 | [Fact] 38 | public async Task Yield_Lazy() 39 | { 40 | var sensors = new WebRequesterAsync(); 41 | 42 | foreach (var result in sensors.OpenStream(new[] { "iddqd", "idkfa", "abba5", "acdc1" })) 43 | { 44 | var data = await result; 45 | 46 | if (data.Data > 23) 47 | break; 48 | 49 | Trace.WriteLine(JsonConvert.SerializeObject(data)); 50 | } 51 | } 52 | 53 | private class Person 54 | { 55 | public string Name { get; set; } 56 | public int Location { get; set; } 57 | public int Salary { get; set; } 58 | } 59 | 60 | [Fact] 61 | public void Linq_Lazy() 62 | { 63 | var employees = new List(10) 64 | { 65 | new Person { Name = "Andy", Location = 1, Salary = 1 }, 66 | new Person { Name = "Thomas", Location = 2, Salary = 1 }, 67 | new Person { Name = "Jefferson", Location = 2, Salary = 2 }, 68 | new Person { Name = "Jim", Location = 2, Salary = 2 }, 69 | new Person { Name = "Ben", Location = 1, Salary = 2 } 70 | }; 71 | 72 | // Where is lazy so no need to create location2 array if not needed 73 | var location2 = employees.Where(e => e.Location == 2); 74 | 75 | // Add next where later 76 | var loc2sal2 = location2.Where(e => e.Salary == 2); 77 | 78 | // Will stop execution on First 79 | var first = loc2sal2.First(); 80 | 81 | // Will go through again the all employees 82 | var last = loc2sal2.Last(); 83 | } 84 | 85 | [Fact] 86 | public async Task Linq_Select_Lazy() 87 | { 88 | // 07_Linq has and example of LINQ Select. This example shows more complicated case with API requests 89 | 90 | // Find first sensor with data value under 23 91 | 92 | var sensorIds = new[] { "iddqd", "idkfa", "abba5", "acdc1" }; 93 | 94 | // Have to use own extension method as Where/First etc. do not support async 95 | var item = await sensorIds.Select(async id => 96 | { 97 | var data = await SensorData.GetSensorAsync(id); 98 | Trace.WriteLine(JsonConvert.SerializeObject(data)); 99 | return data; 100 | }) 101 | .FirstOrDefaultAsync(async s => (await s).Data < 23); 102 | 103 | Trace.WriteLine($"First item: {JsonConvert.SerializeObject(item)}"); 104 | } 105 | 106 | [Fact] 107 | public async Task Lazy_Class() 108 | { 109 | var sensor = new Lazy>(async () => 110 | { 111 | Trace.WriteLine("Fetching data"); 112 | return await SensorData.GetSensorAsync("abba5"); 113 | }); 114 | 115 | Assert.False(sensor.IsValueCreated); 116 | 117 | var data = await sensor.Value; 118 | 119 | var dataOther = await sensor.Value; 120 | } 121 | } 122 | 123 | public static class LinqExtensions 124 | { 125 | public static async Task FirstOrDefaultAsync(this IEnumerable> items, Func, Task> predicate) 126 | { 127 | foreach (var item in items) 128 | { 129 | if (await predicate(item)) 130 | { 131 | return await item; 132 | } 133 | } 134 | 135 | return default; 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /csharp-tutorial/16_PatternMatching.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | namespace csharp_tutorial 6 | { 7 | public class PatternMatching 8 | { 9 | [Fact] 10 | public void Switch() 11 | { 12 | var items = new List { 13 | "Hello", 14 | 1, 15 | 5, 16 | new List { 1, 2, 3 }, 17 | new List { 1, -2 }, 18 | new Square { Side = 8 } 19 | }; 20 | 21 | foreach (var item in items) 22 | { 23 | // NOTE: Switch case will show compiler error if case is already handled 24 | switch (item) 25 | { 26 | case string e: 27 | break; 28 | 29 | case int e when e > 4: 30 | break; 31 | 32 | case int e: 33 | // Try moving this up 34 | break; 35 | 36 | case IEnumerable e when e.All(i => i > 0): 37 | break; 38 | 39 | case IEnumerable e: 40 | break; 41 | 42 | case Square e when e.Side > 5: 43 | break; 44 | 45 | case Square e when e.Side > 5 && e.Side < 10: 46 | // Compiler won't show errors in cases like this 47 | break; 48 | 49 | default: 50 | break; 51 | } 52 | } 53 | } 54 | 55 | [Fact] 56 | public void If_and_casting() 57 | { 58 | var items = new List 59 | { 60 | new Square { Side = 8 }, 61 | new Square { Side = 2 }, 62 | new Circle { Radius = 5}, 63 | new Circle { Radius = 11}, 64 | }; 65 | 66 | foreach (var item in items) 67 | { 68 | if (item is Square s) 69 | { 70 | // Safe casting with as 71 | var a = item as Circle; 72 | var h = item as Square; 73 | 74 | // Unsafe casting 75 | //var a2 = (Circle)item; 76 | } 77 | else if (item is Circle c && c.Radius > 10) 78 | { } 79 | else if (item is Circle ci) 80 | { 81 | // No compiler errors if this is moved up 82 | } 83 | } 84 | } 85 | } 86 | 87 | public class Shape 88 | { } 89 | 90 | public class Square : Shape 91 | { 92 | public int Side { get; set; } 93 | } 94 | 95 | public class Circle : Shape 96 | { 97 | public int Radius { get; set; } 98 | } 99 | } -------------------------------------------------------------------------------- /csharp-tutorial/17_Tuples.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace csharp_tutorial 5 | { 6 | public class Tuples 7 | { 8 | // Tuples fall a little bit to the same category as dynamic. Usually not needed or recommended in the production code, 9 | // as you should use classes instead, but using these while e.g. prototyping is faster 10 | 11 | [Fact] 12 | public void TupleTest() 13 | { 14 | Tuple tp = Tuple.Create("a1", 3); 15 | var tp2 = Tuple.Create("a2", 4); 16 | 17 | // Tuples are immutable 18 | // tp.Item2 = 4; 19 | 20 | Assert.Equal(3, tp.Item2); 21 | Assert.Equal(4, tp2.Item2); 22 | } 23 | 24 | [Fact] 25 | public void ValueTuple() 26 | { 27 | var tp = ("a1", 3); 28 | var tp2 = ("a2", 4); 29 | 30 | // ValueTuples are not immutable 31 | tp.Item2 = 4; 32 | 33 | Assert.Equal(4, tp.Item2); 34 | Assert.Equal(4, tp2.Item2); 35 | 36 | var tpAsTuple = tp.ToTuple(); 37 | 38 | // Immutable again 39 | //tpAsTuple.Item2 = 4; 40 | } 41 | 42 | [Fact] 43 | public void ValueTuple_Names() 44 | { 45 | // Create a ValueTuple with names. 46 | var result = (name: "Timmy", age: 25); 47 | 48 | Assert.Equal("Timmy", result.name); 49 | Assert.Equal(25, result.age); 50 | 51 | var personTuple = GetPersonInfoValueTuple(); 52 | 53 | Assert.Equal("Timmy", personTuple.Item1); 54 | Assert.Equal(25, personTuple.Item2); 55 | 56 | var (name, age) = GetPersonInfoValueTuple(); 57 | 58 | Assert.Equal("Timmy", name); 59 | Assert.Equal(25, age); 60 | 61 | var person = GetPersonInfoNamedTuple(); 62 | 63 | Assert.Equal("Timmy", person.name); 64 | Assert.Equal(25, person.age); 65 | } 66 | 67 | private Tuple GetPersonInfoTuple() 68 | { 69 | var myName = "Timmy"; 70 | var myAge = 25; 71 | return Tuple.Create(myName, myAge); 72 | } 73 | 74 | private (string, int) GetPersonInfoValueTuple() 75 | { 76 | var myName = "Timmy"; 77 | var myAge = 25; 78 | return (myName, myAge); 79 | } 80 | 81 | private (string name, int age) GetPersonInfoNamedTuple() 82 | { 83 | var myName = "Timmy"; 84 | var myAge = 25; 85 | return (myName, myAge); 86 | } 87 | 88 | public class Person 89 | { 90 | private readonly string _firstName; 91 | private readonly string _lastName; 92 | 93 | public Person(string firstName, string lastName) 94 | { 95 | _firstName = firstName; 96 | _lastName = lastName; 97 | } 98 | 99 | public int Age { get; set; } 100 | 101 | public void Deconstruct(out string fullName, out int age) 102 | { 103 | fullName = $"{_firstName} {_lastName}"; 104 | age = Age; 105 | } 106 | } 107 | 108 | [Fact] 109 | public void Deconstruct() 110 | { 111 | var person = new Person("Timmy", "Tester") 112 | { 113 | Age = 30 114 | }; 115 | 116 | var (name, age) = person; 117 | 118 | Assert.Equal("Timmy Tester", name); 119 | Assert.Equal(30, age); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /csharp-tutorial/18_Json.cs: -------------------------------------------------------------------------------- 1 | using csharp_tutorial.Helpers.Hsl; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace csharp_tutorial 10 | { 11 | public class JsonExamples 12 | { 13 | [Fact] 14 | public async Task Get_Hsl_Data() 15 | { 16 | var results = await HslService.GetLocation(HslService.SearchType.VehicleRef, "3606"); 17 | } 18 | } 19 | 20 | public static class HslService 21 | { 22 | public enum SearchType 23 | { 24 | LineRef, 25 | VehicleRef 26 | } 27 | 28 | public static async Task GetLocation(SearchType searchType, string reference) 29 | { 30 | using (var client = new HttpClient()) 31 | { 32 | // NOTE: HSL endpoint is not working anymore. Use hard coded data. 33 | //var jsonData = await client.GetStringAsync("http://dev.hsl.fi/siriaccess/vm/json?ProducerRef=HSL"); 34 | var jsonData = HslJsonSample.Json; 35 | 36 | await Task.Delay(0); 37 | 38 | // Json example can use either SelectToken or indexers 39 | var locations = JObject.Parse(jsonData) 40 | .SelectToken("Siri.ServiceDelivery.VehicleMonitoringDelivery") 41 | .SelectMany(s => s["VehicleActivity"]) 42 | .Where(s => s.SelectToken($"MonitoredVehicleJourney.{searchType.ToString()}.value").ToString() == reference) 43 | .Select(s => s["MonitoredVehicleJourney"]) 44 | .Select(s => new 45 | { 46 | Lon = s["VehicleLocation"]["Longitude"], 47 | Lat = s["VehicleLocation"]["Latitude"] 48 | }) 49 | .FirstOrDefault(); 50 | 51 | // Example with generated model 52 | 53 | var data = JsonConvert.DeserializeObject(jsonData); 54 | 55 | var locationsFromModel = data.Siri.ServiceDelivery.VehicleMonitoringDelivery 56 | .SelectMany(e => e.VehicleActivity) 57 | .Where(e => searchType == SearchType.VehicleRef 58 | ? e.MonitoredVehicleJourney.VehicleRef.Value == reference 59 | : e.MonitoredVehicleJourney.LineRef.Value == reference) 60 | .Select(e => e.MonitoredVehicleJourney) 61 | .Select(e => new 62 | { 63 | Lon = e.VehicleLocation.Longitude, 64 | Lat = e.VehicleLocation.Latitude 65 | }) 66 | .FirstOrDefault(); 67 | 68 | return locations; 69 | 70 | // TODO: Exception handling 71 | 72 | // var description = infoJson["company"]?.FirstOrDefault()?["procurationAbstractDescription"]?.FirstOrDefault(e => e["language"]?.Value() == "Finnish")?["description"]?.Value(); 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /csharp-tutorial/Helpers/Hsl/HslJsonSample.cs: -------------------------------------------------------------------------------- 1 | namespace csharp_tutorial.Helpers.Hsl 2 | { 3 | public class HslJsonSample 4 | { 5 | // GET: http://dev.hsl.fi/siriaccess/vm/json?operatorRef=HSL&lineRef=1058 6 | public static string Json = @" 7 | { 8 | 'Siri': { 9 | 'version': '1.3', 10 | 'ServiceDelivery': { 11 | 'ResponseTimestamp': 1393053418151, 12 | 'ProducerRef': { 13 | 'value': 'HSL' 14 | }, 15 | 'Status': true, 16 | 'MoreData': false, 17 | 'VehicleMonitoringDelivery': [{ 18 | 'version': '1.3', 19 | 'ResponseTimestamp': 1393053418151, 20 | 'Status': true, 21 | 'VehicleActivity': [{ 22 | 'ValidUntilTime': 1393053422091, 23 | 'RecordedAtTime': 1393053392091, 24 | 'MonitoredVehicleJourney': { 25 | 'LineRef': { 26 | 'value': '1058' 27 | }, 28 | 'DirectionRef': {}, 29 | 'FramedVehicleJourneyRef': { 30 | 'DataFrameRef': { 31 | 'value': '2014-02-22' 32 | } 33 | }, 34 | 'OperatorRef': { 35 | 'value': 'HSL' 36 | }, 37 | 'Monitored': true, 38 | 'VehicleLocation': { 39 | 'Longitude': 24.91796, 40 | 'Latitude': 60.18979 41 | }, 42 | 'VehicleRef': { 43 | 'value': '3606' 44 | } 45 | } 46 | }, { 47 | 'ValidUntilTime': 1393053420539, 48 | 'RecordedAtTime': 1393053390539, 49 | 'MonitoredVehicleJourney': { 50 | 'LineRef': { 51 | 'value': '1058' 52 | }, 53 | 'DirectionRef': {}, 54 | 'FramedVehicleJourneyRef': { 55 | 'DataFrameRef': { 56 | 'value': '2014-02-22' 57 | } 58 | }, 59 | 'OperatorRef': { 60 | 'value': 'HSL' 61 | }, 62 | 'Monitored': true, 63 | 'VehicleLocation': { 64 | 'Longitude': 24.87599, 65 | 'Latitude': 60.20583 66 | }, 67 | 'VehicleRef': { 68 | 'value': '3607' 69 | } 70 | } 71 | }, { 72 | 'ValidUntilTime': 1393053447722, 73 | 'RecordedAtTime': 1393053417722, 74 | 'MonitoredVehicleJourney': { 75 | 'LineRef': { 76 | 'value': '1058' 77 | }, 78 | 'DirectionRef': {}, 79 | 'FramedVehicleJourneyRef': { 80 | 'DataFrameRef': { 81 | 'value': '2014-02-22' 82 | } 83 | }, 84 | 'OperatorRef': { 85 | 'value': 'HSL' 86 | }, 87 | 'Monitored': true, 88 | 'VehicleLocation': { 89 | 'Longitude': 25.07629, 90 | 'Latitude': 60.20953 91 | }, 92 | 'VehicleRef': { 93 | 'value': '3608' 94 | } 95 | } 96 | }, { 97 | 'ValidUntilTime': 1393053442526, 98 | 'RecordedAtTime': 1393053412526, 99 | 'MonitoredVehicleJourney': { 100 | 'LineRef': { 101 | 'value': '1058' 102 | }, 103 | 'DirectionRef': {}, 104 | 'FramedVehicleJourneyRef': { 105 | 'DataFrameRef': { 106 | 'value': '2014-02-22' 107 | } 108 | }, 109 | 'OperatorRef': { 110 | 'value': 'HSL' 111 | }, 112 | 'Monitored': true, 113 | 'VehicleLocation': { 114 | 'Longitude': 24.96961, 115 | 'Latitude': 60.18871 116 | }, 117 | 'VehicleRef': { 118 | 'value': '3609' 119 | } 120 | } 121 | }, { 122 | 'ValidUntilTime': 1393053441755, 123 | 'RecordedAtTime': 1393053411755, 124 | 'MonitoredVehicleJourney': { 125 | 'LineRef': { 126 | 'value': '1058' 127 | }, 128 | 'DirectionRef': {}, 129 | 'FramedVehicleJourneyRef': { 130 | 'DataFrameRef': { 131 | 'value': '2014-02-22' 132 | } 133 | }, 134 | 'OperatorRef': { 135 | 'value': 'HSL' 136 | }, 137 | 'Monitored': true, 138 | 'VehicleLocation': { 139 | 'Longitude': 25.0314, 140 | 'Latitude': 60.19454 141 | }, 142 | 'VehicleRef': { 143 | 'value': '3610' 144 | } 145 | } 146 | }, { 147 | 'ValidUntilTime': 1393053447720, 148 | 'RecordedAtTime': 1393053417720, 149 | 'MonitoredVehicleJourney': { 150 | 'LineRef': { 151 | 'value': '1058' 152 | }, 153 | 'DirectionRef': {}, 154 | 'FramedVehicleJourneyRef': { 155 | 'DataFrameRef': { 156 | 'value': '2014-02-22' 157 | } 158 | }, 159 | 'OperatorRef': { 160 | 'value': 'HSL' 161 | }, 162 | 'Monitored': true, 163 | 'VehicleLocation': { 164 | 'Longitude': 24.88326, 165 | 'Latitude': 60.19684 166 | }, 167 | 'VehicleRef': { 168 | 'value': '3612' 169 | } 170 | } 171 | }] 172 | }] 173 | } 174 | } 175 | }"; 176 | } 177 | } -------------------------------------------------------------------------------- /csharp-tutorial/Helpers/Hsl/HslSiriData.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Collections.Generic; 3 | 4 | namespace csharp_tutorial.Helpers.Hsl 5 | { 6 | // Generated with: https://app.quicktype.io/#l=cs&r=json2csharp 7 | 8 | public partial class HslSiriData 9 | { 10 | [JsonProperty("Siri")] 11 | public Siri Siri { get; set; } 12 | } 13 | 14 | public partial class Siri 15 | { 16 | [JsonProperty("version")] 17 | public string Version { get; set; } 18 | 19 | [JsonProperty("ServiceDelivery")] 20 | public ServiceDelivery ServiceDelivery { get; set; } 21 | } 22 | 23 | public partial class ServiceDelivery 24 | { 25 | [JsonProperty("ResponseTimestamp")] 26 | public long ResponseTimestamp { get; set; } 27 | 28 | [JsonProperty("ProducerRef")] 29 | public Ref ProducerRef { get; set; } 30 | 31 | [JsonProperty("Status")] 32 | public bool Status { get; set; } 33 | 34 | [JsonProperty("MoreData")] 35 | public bool MoreData { get; set; } 36 | 37 | [JsonProperty("VehicleMonitoringDelivery")] 38 | public List VehicleMonitoringDelivery { get; set; } 39 | } 40 | 41 | public partial class Ref 42 | { 43 | [JsonProperty("value")] 44 | public string Value { get; set; } 45 | } 46 | 47 | public partial class VehicleMonitoringDelivery 48 | { 49 | [JsonProperty("version")] 50 | public string Version { get; set; } 51 | 52 | [JsonProperty("ResponseTimestamp")] 53 | public long ResponseTimestamp { get; set; } 54 | 55 | [JsonProperty("Status")] 56 | public bool Status { get; set; } 57 | 58 | [JsonProperty("VehicleActivity")] 59 | public List VehicleActivity { get; set; } 60 | } 61 | 62 | public partial class VehicleActivity 63 | { 64 | [JsonProperty("ValidUntilTime")] 65 | public long ValidUntilTime { get; set; } 66 | 67 | [JsonProperty("RecordedAtTime")] 68 | public long RecordedAtTime { get; set; } 69 | 70 | [JsonProperty("MonitoredVehicleJourney")] 71 | public MonitoredVehicleJourney MonitoredVehicleJourney { get; set; } 72 | } 73 | 74 | public partial class MonitoredVehicleJourney 75 | { 76 | [JsonProperty("LineRef")] 77 | public Ref LineRef { get; set; } 78 | 79 | [JsonProperty("DirectionRef")] 80 | public DirectionRef DirectionRef { get; set; } 81 | 82 | [JsonProperty("FramedVehicleJourneyRef")] 83 | public FramedVehicleJourneyRef FramedVehicleJourneyRef { get; set; } 84 | 85 | [JsonProperty("OperatorRef")] 86 | public Ref OperatorRef { get; set; } 87 | 88 | [JsonProperty("Monitored")] 89 | public bool Monitored { get; set; } 90 | 91 | [JsonProperty("VehicleLocation")] 92 | public VehicleLocation VehicleLocation { get; set; } 93 | 94 | [JsonProperty("VehicleRef")] 95 | public Ref VehicleRef { get; set; } 96 | } 97 | 98 | public partial class DirectionRef 99 | { 100 | } 101 | 102 | public partial class FramedVehicleJourneyRef 103 | { 104 | [JsonProperty("DataFrameRef")] 105 | public Ref DataFrameRef { get; set; } 106 | } 107 | 108 | public partial class VehicleLocation 109 | { 110 | [JsonProperty("Longitude")] 111 | public double Longitude { get; set; } 112 | 113 | [JsonProperty("Latitude")] 114 | public double Latitude { get; set; } 115 | } 116 | } -------------------------------------------------------------------------------- /csharp-tutorial/Helpers/SensorData.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.IO; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | namespace csharp_tutorial 8 | { 9 | public static class SensorData 10 | { 11 | private const string URL = "http://dummy-sensors.azurewebsites.net/api/sensor/"; 12 | 13 | public static double GetDataSync(string sensorId = "iddqd") 14 | { 15 | var request = HttpWebRequest.Create($"{URL}{sensorId}"); 16 | request.Method = "GET"; 17 | 18 | using (var response = request.GetResponse()) 19 | { 20 | var dataStream = response.GetResponseStream(); 21 | var reader = new StreamReader(dataStream); 22 | 23 | var sensorJson = reader.ReadToEnd(); 24 | 25 | reader.Close(); 26 | dataStream.Close(); 27 | 28 | dynamic sensor = JsonConvert.DeserializeObject(sensorJson); 29 | 30 | return sensor.data; 31 | } 32 | } 33 | 34 | public static SensorDto GetSensorSync(string sensorId = "iddqd") 35 | { 36 | var request = HttpWebRequest.Create($"{URL}{sensorId}"); 37 | request.Method = "GET"; 38 | 39 | using (var response = request.GetResponse()) 40 | { 41 | var dataStream = response.GetResponseStream(); 42 | var reader = new StreamReader(dataStream); 43 | 44 | var sensorJson = reader.ReadToEnd(); 45 | 46 | reader.Close(); 47 | dataStream.Close(); 48 | 49 | return JsonConvert.DeserializeObject(sensorJson); 50 | } 51 | } 52 | 53 | /* 54 | public static double GetDataSync(string sensrorId = "iddqd") 55 | { 56 | using (var client = new HttpClient()) 57 | { 58 | // HttpClient doesn't have sync methods, so call GetAsync in an own thread 59 | // If you need sync http methods you can also use WebRequest 60 | var response = Task.Run(() => client.GetAsync($"{_url}{sensrorId}")).GetAwaiter().GetResult(); 61 | 62 | if (!response.IsSuccessStatusCode) 63 | return double.MinValue; 64 | 65 | var sensorJson = response.Content.ReadAsStringAsync().Result; 66 | dynamic sensor = JsonConvert.DeserializeObject(sensorJson); 67 | return sensor.data; 68 | } 69 | } 70 | */ 71 | 72 | public static async Task GetDataAsync(string sensrorId = "iddqd") 73 | { 74 | using (var client = new HttpClient()) 75 | { 76 | var response = await client.GetAsync($"{URL}{sensrorId}"); 77 | 78 | if (!response.IsSuccessStatusCode) 79 | return double.MinValue; 80 | 81 | var sensorJson = await response.Content.ReadAsStringAsync(); 82 | dynamic sensor = JsonConvert.DeserializeObject(sensorJson); 83 | return sensor.data; 84 | } 85 | } 86 | 87 | public static async Task GetSensorAsync(string sensrorId = "iddqd") 88 | { 89 | using (var client = new HttpClient()) 90 | { 91 | var response = await client.GetAsync($"{URL}{sensrorId}"); 92 | 93 | if (!response.IsSuccessStatusCode) 94 | return null; 95 | 96 | var sensorJson = await response.Content.ReadAsStringAsync(); 97 | return JsonConvert.DeserializeObject(sensorJson); 98 | } 99 | } 100 | } 101 | 102 | public class SensorDto 103 | { 104 | public string Id { get; set; } 105 | public double Data { get; set; } 106 | } 107 | } -------------------------------------------------------------------------------- /csharp-tutorial/csharp-tutorial.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------