ABOUT ME
ABOUT ME
I am a programmer/developer of more than 30 years experience, began as a raw electronics designer, and soon started assembly, and then C++, Win32 API (still my favorites), Java, Android apps, C# Desktop applications on security, C# ASP.NET classic, C# ASP.NET Core.
What's New:
08-Dec-2022 - Add Chapter - Project on Audio Extractor with FFMPEG
26-Nov-2022 - Added Chapter - Project on Classroom Voting
11-Nov-2022 - Added Chapter - Project on CSV Processing
C# is no doubt a long way from its initial introduction 20 years ago.
The syntax at that time was simple - anybody knowing C, Java, C++ could easily get started with C# because the syntax of loops, conditions, inheritance, etc., were so similar.
But you must have already noticed that these days a professionally written C# code invariably contains many puzzling punctuators spread here and there - like an exclamation after default - default. or exclamation followed by a dot ?. or => or ?? etc.,. There are many queer looking concepts like statement lambdas, patterns, records, and many-many more like them.
You have probably joined me because you want to settle this once for all. I have put a lot of effort in constructing this course to see you through in a small amount of time. PLEASE WRITE TO ME IF YOU FACE ANY PROBLEMS. I WOULD BE HAPPY TO HELP.
CHAPTER➤ It's the entry point of your program which means that it is the first function that executes when your program starts its execution. In this tutorial we learn the syntax of this function; we also learn about top-level statements that provide a shortcut to the main function.
CHAPTER➤ In this tutorial we have a quick look at a class in C#. Then we have a discussion of the structs, and then a detailed look on the record keyword. The tutorial closes with "When to use a record".
CHAPTER➤ So, this tutorial is a review for the sake of completeness. We have discussed Declaration Statements, Expression Statements, Empty Statement, Nested Statement Blocks, Selection Statements, Iteration Statements, Jumping Statements, Exception Statements, await Statements, fixed Statements and lock Statements.
CHAPTER➤ This class also contains an enumeration of two members - Meter and Centimeter - to specify the unit of the quantity stored. Add a constructor and a public function called Convert. The Convert function converts the data to meter if the data held is in centimeters, and to centimeters if the data held is in meters. The Convert function displays the result correct to 2 decimal places. Test the function by creating an object in the main function.
CHAPTER➤ If you are familiar with C and C++, then you can think of reference types as that hold address of objects created on a heap memory. In contrast the value types hold an object. These concepts are far too complex for a beginner's tutorial. So I will try to explain them from a practical standpoint.
CHAPTER➤ The properties are readonly. The corresponding data-type is generated by the compiler. The name of the data-type remains unknown. It is not accessible in source code. Such types are called anonymous types. They are used to quickly store data without the need of defining a data-type. They are also used in query expressions.
CHAPTER➤
CHAPTER➤. AND ?[]) Programmers use an if-condition as a safety net and make a null-test on an object that has a possibility of being null at that point. The null conditional operators make it easier to write the code without the if-condition.
CHAPTER➤ Inside main create an array of 10 items. Use a for-loop to fill the array. Each item can be filled with random data. Then, use a select query on the array to project two fields only - Student ID and marks in the first subject. Print the queried data with a suitable loop. Use var keyword as far as possible.
CHAPTER➤ Write three programs (1) Write a program to count the number of 'a' in this string. (2) Write a program to extract the numeric part into a string variable and display it. (3) Replace all vowels by underscore.
CHAPTER➤ They can exist inside a class, struct, record - but not globally outside. So every function, including the Main function must be declared inside a class or a struct. In this tutorial we examine default arguments, named arguments, passing parameters by value and reference, the use of in keyword and finally passing unspecified number of arguments with the params keyword.
CHAPTER➤ In this context a function signature includes the return type and the arguments of a function, but doesn't include the name. We also learn about lambda expressions and built-in delegates called Action and Func delegates.
CHAPTER➤ Properties provide setter and getter functions for validation of data before it enters or leaves your object. A property has a specific syntax that needs to be mastered.
CHAPTER➤ Thus, we can simplify such functions by removing the braces and the return keyword. The result is that the overall look of the function becomes simplified, and it becomes easier to read. Functions, properties, operators, and indexers that use this syntax are called expression-bodied members.
CHAPTER➤)". This signature matches the signature of the TimerCallback delegate defined in the System.Threading.Timer class of dotnet. The "void Tick" function prints the current system time on the console (use DateTimeNow). Inside Main create a System.Threading.Timer by using a suitable constructor that calls "void Tick" once every second. Run the program to verify that you have a clock that prints the current system time. You will have to study the documentation of System.Threading.Timer class.
CHAPTER➤ This class should contain an event delegate that takes one argument of Int32 type. The class MyNumber should raise this event when the Number property changes. The changed value should be sent as an argument to all delegates that register to this event. Inside Main create an object of MyNumber and attach an event handler that prints the changed value. The event handler should be an anonymous expression lambda that receives an Int32 argument and prints the number to console. Test the project by changing the Number property. If you did the things correctly, then you should see the event handler called. This might be a difficult one. So please go through the solution if you find the question itself difficult to understand.
CHAPTER➤ In this tutorial we take a short example to get introduced to the syntax. We also learn about the protected and sealed keywords.
CHAPTER➤ I won't be able to go into the philosophy of polymorphism. The whole point of this tutorial is to introduce you to the syntax, and about how the usual things are done in a C# syntax.
CHAPTER➤ The concept of a static class and static members is essentially the same as in other object oriented languages like C++ and Java. static classes provide simplicity but they are not a part of pure object oriented design. The general guidelines are to use static classes only In this tutorial we learn about the features of a static class in C#.
CHAPTER➤ A const member behaves like a static, but it's value is marked as fixed and constant. The compiler knows its value. Therefore it can make substitutions and avoid memory allocation for a const member. Let's see the finer details.
CHAPTER➤ Create a class called CBankAccount that contains a static member to hold the name of the bank, a constant member to store the minimum balance (100) that a customer has to maintain, another constant member for rate of interest (5) and a yet another constant member for periodicity (4 months) of applying interest. Also add an instance member to hold the balance of a customer in dollars. Add a static constructor to initialize the name of the bank by obtaining input from the user. Add a static function called RenameBank that can be used to give a new name to the bank. Add two functions called Withdraw(int amount) and Deposit(int amount) that update the balance after a successful transaction and print it. Also add a function AddInterest() that calculates simple interest for four months and updates the balance. Test the class in Main.
CHAPTER➤ GENERICS
In this tutorial we take a hypothetical problem to build towards the concept of generics. First we explore a few bad solutions. Then we explain a perfect solution by generics. That leads us to list the benefits of using generics. We conclude with the constraints on generic parameters.
CHAPTER➤ ARRAYS - An array can be of one dimension, can be of multiple dimensions and can even be a jagged array, which is commonly called an array of arrays. The size of an array is decided at the time of creation, and cannot be altered later.
CHAPTER➤ We have seen that arrays also hold objects. The size of an array is fixed at the time of creation, but collections can grow and shrink as per need. Some collections can assign keys to the objects they hold. The elements can be accessed with a key like we have a dictionary.
CHAPTER➤ LINQ - After that we discuss the internal workings of this interface. We demonstrate how the yield keyword can be used to return an IEnumerable from a function. The tutorial concludes with a C# program to efficiently print a fibonacci series by using the yield keyword and statement.
CHAPTER➤ LINQ - A program has been used to explain these functions. This will help you get started but experience is the best teacher. So all I can help you is get started.
CHAPTER➤ LINQ - Use FirstOrDefault for finding the first element that matches a condition. It returns the default value if no matching records exist. "First" does the same thing but throws an exception instead. SingleOrDefault returns the default value if exactly one record cannot be found. There is one more called Find which should be used if the search has to be by a primary key value.
CHAPTER➤ LINQ - GROUPBY
C# Linq provides a GroupBy extension method for grouping records on a common key. This tutorial starts with a brief introduction to the concept of grouping. Then we use the GroupBy extension to group various records on a property called last name so that customers of the same last name are grouped together. The result is then displayed on console.
CHAPTER➤ LINQ - JOIN
Two lists can be joined on common key or keys by using the Join extension method. The concept of a join in linq is exactly the same as in SQL of databases. A detailed concept of joins is far too complex to be described in a single tutorial. So this tutorial has been kept simple to help you get started and walk you to a point where a further study is easier.
CHAPTER➤ Add 3 items to this list - (ID:1, Name:"For Men"), (2, "For Women") and (3, "For Kids"). Then create a list of SubCategory records with ID, Name and CategoryFK as properties. The property CategoryFK is a foreign key for the ID property of a Category. Add these items - (ID:1, CategoryFK:1, Name:"Shirts"), (2, 1, "T Shirts"), (3, 1, "Trousers"), (4, 2, "Skirts"), (5, 2, "Shoes"). Use the GroupJoin extension to present dresses of men under a heading "For Men", dresses of women under "For Women" and likewise for kids.
CHAPTER➤ EF CORE - It is best done with the help of EF Core libraries. The good thing is that the steps for database connectivity are fairly standardized now. The same steps are used for C# console applications, the same are for a C# winforms, C# WPF and even C# web applications. This tutorial walks you through the steps required for creating a database. In the next tutorials we shall explain the create, read, update and delete operations.
CHAPTER➤ EF CORE - We shall now add a blog record to the table and save it. Then we shall extract it back and make a change to the Heading property, and save it again. Then we shall extract it back and delete it from the table. Finally, we shall display the count of records in the table, and verify that it is shown zero.
CHAPTER➤ They can be thought of identifiers that receive an assignment, but the assignment is discarded and not available for use. A discard is most commonly used to indicate that the return type of a function has been ignored intentionally. But there are other less known use-cases of a discard. This tutorial demonstrates them by using various program examples - using a discard to (1) ignore a function return, (2) ignore the return of a tuple, (3) ignore an out parameter of a function, (4) default case of a switch expression and (5) as a forced assignment.
CHAPTER➤ In this tutorial we take various snippets to learn expression matching. One of the uses is to test if a variable matches a data type. Pattern matching is also used in switch expressions to match the arms against constants, strings or logical conditions. Pattern matching is also used for comparison of multiple inputs passed as expressions of tuple types. Lists can also be matched if you are using C# 11 or later.
CHAPTER➤ For example, we can use reflection to query the constructors and methods of the System.String class. We can even use Reflection to execute the methods queried through Reflection. We can use Reflection to obtain a description about the assembly that contains a given Type. Please do, however, take note that this is not something that is done very commonly - it's available when you need it.
CHAPTER➤ All these tasks involve time consuming operations that make the main thread too busy. This causes the application to hang and freeze while the main thread waits for a large file to download. The solution to this problem lies in asynchronous task programming. A potentially long and time consuming operation can be run in parallel in such a way that the UI (user interface) remains responsive and, possibly, provides a progress feedback to the user. Let's explore it in this tutorial.
CHAPTER➤ The functions that are expected to fail are written inside try blocks with one or more catch blocks. Each catch block handles a specific reason of failure. A catch block receives an exception object which is often used to obtain a detailed information of the cause of failure.
CHAPTER➤ PROJECT EXERCISE ON CSV Each record is of the format ABCD,N where ABCD is the code of a supplier and N is a sequence of digits for the amount billed. For example Your program has to read all the records and output a report in a file where all the records are arranged in the increasing order of the amount. The last line shows the sum total. If there is an error in any record the program should exit by printing "Error at record ".
Possible benefits to you -
Learn HTTP Server and LAN Interfacing
Use for enhancing your resume.
As a business idea.
Possible benefits to you -
Interfacing C# Console to FFMPEG
Learning audio extraction FFMPEG commands
Use for enhancing your resume.
Use in your future projects like video processing etc.,
This is a walkthrough on creating your first program with C# .NET Core!
Main method is the equivalent of the main method as we know in C language. It's the entry point of your program which means that it is the first function that executes when your program starts its execution. In this tutorial we learn the syntax of this function; we also learn about top-level statements that provide a shortcut to the main function.
We expect you have a very general understanding of object oriented programming blocks like classes and structs. In this tutorial we have a quick look at a class in C#. Then we have a discussion of the structs, and then a detailed look on the record keyword. The tutorial closes with "When to use a record".
The concept of statements and expressions in C# is exactly the same as in other programming languages. So, this tutorial is a review for the sake of completeness. We have discussed Declaration Statements, Expression Statements, Empty Statement, Nested Statement Blocks, Selection Statements, Iteration Statements, Jumping Statements, Exception Statements, await Statements, fixed Statements and lock Statements.
Write a C# class called CData to hold a float type of quantity. This class also contains an enumeration of two members - Meter and Centimeter - to specify the unit of the quantity stored. Add a constructor and a public function called Convert. The Convert function converts the data to meter if the data held is in centimeters, and to centimeters if the data held is in meters. The Convert function displays the result correct to 2 decimal places. Test the function by creating an object in the main function.
Value Types and Reference types are two types in C#. If you are familiar with C and C++, then you can think of reference types as that hold address of objects created on a heap memory. In contrast the value types hold an object. These concepts are far too complex for a beginner's tutorial. So I will try to explain them from a practical standpoint.
An anonymous type is described by a comma separated properties clubbed into curly braces. The properties are readonly. The corresponding data-type is generated by the compiler. The name of the data-type remains unknown. It is not accessible in source code. Such types are called anonymous types. They are used to quickly store data without the need of defining a data-type. They are also used in query expressions.
In this tutorial we take common topics on strings, such as creating strings, on concatenation of strings, extraction of a substring, and truncation, null testing, searching a string within another, and comparison of two strings.
An object reference might be null when your code calls a function or accesses a property. Programmers use an if-condition as a safety net and make a null-test on an object that has a possibility of being null at that point. The null conditional operators make it easier to write the code without the if-condition.
We have to store four properties of a record - student id of string type and marks in three subjects of Int32 type. Inside main create an array of 10 items. Use a for-loop to fill the array. Each item can be filled with random data. Then, use a select query on the array to project two fields only - Student ID and marks in the first subject. Print the queried data with a suitable loop. Use var keyword as far as possible.
Suppose there is a string s = "aubcd123paqeaw". Write three programs (1) Write a program to count the number of 'a' in this string. (2) Write a program to extract the numeric part into a string variable and display it. (3) Replace all vowels by underscore.
Functions in C# are usually called Methods. They can exist inside a class, struct, record - but not globally outside. So every function, including the Main function must be declared inside a class or a struct. In this tutorial we examine default arguments, named arguments, passing parameters by value and reference, the use of in keyword and finally passing unspecified number of arguments with the params keyword.
In this tutorial we learn about delegates that are a data-type for a function signature. In this context a function signature includes the return type and the arguments of a function, but doesn't include the name. We also learn about lambda expressions and built-in delegates called Action and Func delegates.
The concept of a property is the most widely used in dotnet programming. Properties provide setter and getter functions for validation of data before it enters or leaves your object. A property has a specific syntax that needs to be mastered.
If a function consists of only one statement or expression, then the braces and the return keyword could be redundant. Thus, we can simplify such functions by removing the braces and the return keyword. The result is that the overall look of the function becomes simplified, and it becomes easier to read. Functions, properties, operators, and indexers that use this syntax are called expression-bodied members.
Create a class MyClock that contains only one public function "void Tick(Object?)". This signature matches the signature of the TimerCallback delegate defined in the System.Threading.Timer class of dotnet. The "void Tick" function prints the current system time on the console (use DateTime.Now). Inside Main create a System.Threading.Timer by using a suitable constructor that calls "void Tick" once every second. Run the program to verify that you have a clock that prints the current system time. You will have to study the documentation of System.Threading.Timer class.
Create a class called MyNumber with a property called Number of Int32 type. This class should contain an event delegate that takes one argument of Int32 type. The class MyNumber should raise this event when the Number property changes. The changed value should be sent as an argument to all delegates that register to this event. Inside Main create an object of MyNumber and attach an event handler that prints the changed value. The event handler should be an anonymous expression lambda that receives an Int32 argument and prints the number to console. Test the project by changing the Number property. If you did the things correctly, then you should see the event handler called. This might be a difficult one. So please go through the solution if you find the question itself difficult to understand.
Inheritance in C# is similar to what we have in other object oriented languages. In this tutorial we take a short example to get introduced to the syntax. We also learn about the protected and sealed keywords.
This tutorial is for explaining a few programs that exhibit the idea of polymorphism. I won't be able to go into the philosophy of polymorphism. The whole point of this tutorial is to introduce you to the syntax, and about how the usual things are done in a C# syntax.
A static class is a container for self-contained static functions that just operate on the input operators. The concept of a static class and static members is essentially the same as in other object oriented languages like C++ and Java. static classes provide simplicity but they are not a part of pure object oriented design. The general guidelines are to use static classes only SPARINGLY in your projects. In this tutorial we learn about the features of a static class in C#.
Only one copy of a static data member exists in the entire program. A const member behaves like a static, but it's value is marked as fixed and constant. The compiler knows its value. Therefore it can make substitutions and avoid memory allocation for a const member. Let's see the finer details.
An interface is a collection of related functionality. An audio player is an audio player only if it implements the ability to play, pause, stop, etc., We can say that the functions play, pause, stop are together an interface called IAudioControls. An interface is a set of functions grouped together under the interface keyword. An interface cannot contain instance data members - they can, however, contain static data members - but that's an entirely different thing because static members are tied to a class or interface, not to a specific object. In this tutorial we examine various aspects of interfaces from C# perspective.
(C# Language) Practice Exercise in C# - static and const | This exercise is an exercise just for the sake of practice in static and consts in a class. Create a class called CBankAccount that contains a static member to hold the name of the bank, a constant member to store the minimum balance (100) that a customer has to maintain, another constant member for rate of interest (5) and a yet another constant member for periodicity (4 months) of applying interest. Also add an instance member to hold the balance of a customer in dollars. Add a static constructor to initialize the name of the bank by obtaining input from the user. Add a static function called RenameBank that can be used to give a new name to the bank. Add two functions called Withdraw(int amount) and Deposit(int amount) that update the balance after a successful transaction and print it. Also add a function AddInterest() that calculates simple interest for four months and updates the balance. Test the class in Main. (hoven.in)
In this tutorial we take a hypothetical problem to build towards the concept of generics. First we explore a few bad solutions. Then we explain a perfect solution by generics. That leads us to list the benefits of using generics. We conclude with the constraints on generic parameters.
Array can be used to store many variables of the same type. An array can be of one dimension, can be of multiple dimensions and can even be a jagged array, which is commonly called an array of arrays. The size of an array is decided at the time of creation, and cannot be altered later.
A collection class holds objects. We have seen that arrays also hold objects. The size of an array is fixed at the time of creation, but collections can grow and shrink as per need. Some collections can assign keys to the objects they hold. The elements can be accessed with a key like we have a dictionary.
This tutorial introduces you to the IEnumerable interface and its practical significance. After that we discuss the internal workings of this interface. We demonstrate how the yield keyword can be used to return an IEnumerable from a function. The tutorial concludes with a C# program to efficiently print a fibonacci series by using the yield keyword and statement.
In this tutorial we learn the use of Select, Where, OrderBy and OrderByDescending functions provided by C# LINQ. A program has been used to explain these functions. This will help you get started but experience is the best teacher. So all I can help you is get started.
In this tutorial we learn about FirstOrDefault, Single, First and SingleOrDefault extension methods available on an IEnumerable. Use FirstOrDefault for finding the first element that matches a condition. It returns the default value if no matching records exist. "First" does the same thing but throws an exception instead. SingleOrDefault returns the default value if exactly one record cannot be found. There is one more called Find which should be used if the search has to be by a primary key value.
C# Linq provides a GroupBy extension method for grouping records on a common key. This tutorial starts with a brief introduction to the concept of grouping. Then we use the GroupBy extension to group various records on a property called last name so that customers of the same last name are grouped together. The result is then displayed on console.
Two lists can be joined on common key or keys by using the Join extension method. The concept of a join in linq is exactly the same as in SQL of databases. A detailed concept of joins is far too complex to be described in a single tutorial. So this tutorial has been kept simple to help you get started and walk you to a point where a further study is easier.
Create a list of Category records with ID and Name as properties. Add 3 items to this list - (ID:1, Name:"For Men"), (2, "For Women") and (3, "For Kids"). Then create a list of SubCategory records with ID, Name and CategoryFK as properties. The property CategoryFK is a foreign key for the ID property of a Category. Add these items - (ID:1, CategoryFK:1, Name:"Shirts"), (2, 1, "T Shirts"), (3, 1, "Trousers"), (4, 2, "Skirts"), (5, 2, "Shoes"). Use the GroupJoin extension to present dresses of men under a heading "For Men", dresses of women under "For Women" and likewise for kids.
Database connectivity is required in almost every project. It is best done with the help of EF Core libraries. The good thing is that the steps for database connectivity are fairly standardized now. The same steps are used for C# console applications, the same are for a C# winforms, C# WPF and even C# web applications. This tutorial walks you through the steps required for creating a database. In the next tutorials we shall explain the create, read, update and delete operations.
This tutorial continues from the previous tutorial where we created a database and a table in it. We shall now add a blog record to the table and save it. Then we shall extract it back and make a change to the Heading property, and save it again. Then we shall extract it back and delete it from the table. Finally, we shall display the count of records in the table, and verify that it is shown zero.
Discards are denoted by an underscore. They can be thought of identifiers that receive an assignment, but the assignment is discarded and not available for use. A discard is most commonly used to indicate that the return type of a function has been ignored intentionally. But there are other less known use-cases of a discard. This tutorial demonstrates them by using various program examples - using a discard to (1) ignore a function return, (2) ignore the return of a tuple, (3) ignore an out parameter of a function, (4) default case of a switch expression and (5) as a forced assignment.
(C# Language) Functional Techniques - Pattern Matching | Pattern matching is used to test an expression. In this tutorial we take various snippets to learn expression matching. One of the uses is to test if a variable matches a data type. Pattern matching is also used in switch expressions to match the arms against constants, strings or logical conditions. Pattern matching is also used for comparison of multiple inputs passed as expressions of tuple types. Lists can also be matched if you are using C# 11 or later. (hoven.in)
Reflection is used to obtain information about the functions, properties, events, fields of a given Type of dotnet. For example, we can use reflection to query the constructors and methods of the System.String class. We can even use Reflection to execute the methods queried through Reflection. We can use Reflection to obtain a description about the assembly that contains a given Type. Please do, however, take note that this is not something that is done very commonly - it's available when you need it.
An application is often required to make database access, perform file read and write or contact internet for communication. All these tasks involve time consuming operations that make the main thread too busy. This causes the application to hang and freeze while the main thread waits for a large file to download. The solution to this problem lies in asynchronous task programming. A potentially long and time consuming operation can be run in parallel in such a way that the UI (user interface) remains responsive and, possibly, provides a progress feedback to the user. Let's explore it in this tutorial!
A function throws an exception on failure. The functions that are expected to fail are written inside try blocks with one or more catch blocks. Each catch block handles a specific reason of failure. A catch block receives an exception object which is often used to obtain a detailed information of the cause of failure.
This project creates an HTTP Server on a Windows PC. The server presents an html page to a connecting client. The page contains html and javascript for communication with the server. The server is capable of serving json to ajax requests coming from the html page. A mobile phone can be used to connect to the application. The phone must be on the same wifi network. Let's see the basic pre-requisites and the broader function now.
This is the first step of writing the code for the classroom voting application. In this tutorial we shall add a model class to hold voting data. We shall also add a project context for EF Core connectivity. Finally, we shall add migrations, and then apply the migrations. We shall then verify that the database get created. There are two pre-requisites that you must be aware because I shall assume that you are already familiar with C#. For this you can refer the list of my courses and locate the course on C#. Secondly you must also go through the chapter on Entity Framework Core for Console Applications.
This is the second step of writing our project on classroom voting application. We shall now set an http listener to listen and respond to requests coming from a browser. For this, dotnet core provides a class called HttpListener. Most of the basic plumbing is handled by this class. We need only four things to get it working. First we have to add the listening routes to the Prefixes collection - wildcards are also supported. After that the listener is put into started mode, and a while loop is set to handle the requests. We have to provide an AsyncCallback delegate to handle a request, and write the response to the outputstream. This tutorial explains all the steps in sufficient detail. Let's see!
A text file contains an unknown number of records. Each record is of the format ABCD,N where ABCD is the code of a supplier and N is a sequence of digits for the amount billed. For example CHD1,34678 is a record with a supplier code of CHD1 and billed amount of 34678. Your program has to read all the records and output a report in a file where all the records are arranged in the increasing order of the amount. The last line shows the sum total. If there is an error in any record the program should exit by printing "Error at record--- ".
OpenCourser helps millions of learners each year. People visit us to learn workspace skills, ace their exams, and nurture their curiosity.
Our extensive catalog contains over 50,000 courses and twice as many books. Browse by search, by topic, or even by career interests. We'll match you to the right resources quickly.
Find this site helpful? Tell a friend about us.
We're supported by our community of learners. When you purchase or subscribe to courses and programs or purchase books, we may earn a commission from our partners.
Your purchases help us maintain our catalog and keep our servers humming without ads.
Thank you for supporting OpenCourser.