jay's old blog

this blog will be deleted soon - please visit my new blog - https://thesanguinetechtrainer.com

Regular Expressions

Regular expressions become useful when you are working with patterns. The real question is, why and when would you use these regular expressions?  

The idea behind regular expressions is to look for patterns, in strings. You see, almost each and every type of data is almost always a string. We store data in string collections. Imagine we are identifying everybody in a city. We would have millions of records, each stored as some string data. Suppose, we are looking at finding out everybody who is a female, has a particular age, and lived at a specific address. Let's assume that the whole data is stored (or at least retrieved) as one single string. 

If so, we simply need to run through the entire millions of records looking for a pattern that matches our requirement.  

C sharp of course, provides you with support for regular expressions. Now, here is a sample.  

Match temp_match = Regex.Match(input_string,@"(study nildana)"); 

Here, the regular expression is @"(study nildana)". This regular expression will find any string that contains the word 'study nildana' in the string that was input.  

Now, here is another thing. When it comes to writing regular expressions, it is best if you don’t do it yourself. Just go ahead and use some websites that will help you automatically generate the regular expression you are looking. 

Now now, don’t think as cheating. Think of it like taking assistance. Remember, you are a developer. Not a regular expression writer. 

I personally recommend the website regexr.com                                                      

Input Validation

Eventually, we will be blogging a lot (like a lot) on data access. At that time we will go deep into input validation. Right now, I will give the essence of input validation.  

Input validation is about taking the necessary measures to make sure that the user input itself does not compromise the integrity of your application. As always, lets go with an example here.  

Consider a simple house appliance such as a refrigerator. Most folks will end up using a stabilizer because, sometimes, there will be a power surge. When that happens, the stabilizer will kill itself, so that the refrigerator will not. If the power supply is good, then the stabilizer will pass on the power to the refrigerator. This, in essence, is input validation.  

Let's go with another example. If you have a petrol car, would you pour diesel in it? If you do, your engine will go bust. That's because, the wrong input can screw things up really bad. To prevent that, we use input validation.  

In software, same thing can happen. Suppose, there is an input box that is designed to accept phone numbers. What if, the user enters a name instead of a number? Your application would crash. However, you could apply the concepts of input validation. Perhaps, you could modify the keyboard that pops up so that if the user enters anything other than a number, it won't register.  

Of course, that is just one type of input validation. Suppose you ensure that the keyboard only gives numbers. However, the user can still enter too many numbers or not enter any number at all. If so, you would have to write some sort of a code that checks if the user has entered the right number of digits in the input box. If it is correct, you proceed to the next step. If it is wrong, you display some kind of error message.  

Like this, anytime you receive any sorts of input, from the user or from some external source, you 'validate' it. Have checks in place to make sure that 'unwanted' data does not reach the processing stage of the app. By doing so, you can avoid the app from crashing. 

Delegates and Anonymous Methods

When you end up building complicated software, you will mostly likely end up using a lot of events (which allow you to react to triggers) and delegates. That is why, we will discuss Delegates here, first. Then, we will move ourselves up to events. Of course, we are also talking about anonymous methods here.  

This topic can be tricky, so you need to read this multiple times. This blog post, I mean.  

Previously, when discussing types, we talked about pointers. We said that we won't use pointer types, and that is actually one of the cool things about c sharp. Now, we are talking about delegates which are also pointers. The distinction here is that, delegates point to a method instead of pointing to a memory location. In fact, delegates can point to multiple methods.  

How does this work really?  

Well, let's say you are working on a project, like organizing a trip. You assign a different task (collecting money, buying food, arranging vehicle) to each member of your trip. Since each person is doing some task, we can think of them as methods.  

Let's say, you find yourself in a scenario where you have one member (let's call her rita) of the trip who is not assigned any particular task. However, he has a good rapport with the other members. What you do is, whenever you want to contact these trip members, you contact rita. Rita in turn contacts the members. So, you 'delegate' some method related work to someone who in turns talks to the actual method.  

Let's get in some code here and see how it works.  

I have a standard method here 

        public static string add_two_strings(string a,string b) 
            string final_string = ""; 
            final_string = a + b; 
            return final_string; 

Now, I am going to define a delegate.  

public delegate string combine_strings(string a, string b); 

Here, note how the delegate does not have body. It is imply defined, with a single line statement. Similar to how you would declare objects.  

Now, I will point this delegate object to the method. 

combine_strings temp_delegate = add_two_strings; //here combine_strings is delegate we defined earlier and temp_delegate is its object 

Now, I can call the actual method via the delegate. 

string temp_delegate_answer = temp_delegate("hello ", "delegate"); 

Here, this is happening. 

  1. First, temp_delegate will accept the parameters. 

  1. Second, temp_delegate will in turn call the actual method which is add_two_strings. It also passes the parameters. 

  1. The add_two_strings method will return a value to the delegate.  

  1. The object temp_delegate_answer will then accept the returned value from the delegate. 

Since delegates are pointers, they can point to one or more methods. When you call a delegate object, all the methods that it points to also get called. See the code repo for a more in depth at how this works.  

Before I do my analysis of delegates, let's look at anonymous methods which use lambda expressions. Anonymous methods are those that don’t have a name of their own. Further, they are almost always called by a delegate and delegate always points at them. That is why, they are not given any name as such. They are defined, and immediately, a delegate is made to point at them.  

In order to define these anonymous methods, we will be using lambda expressions. Don’t get confused over the word 'lambda' though. Just like boolean expressions are for taking decisions, lambda expressions are for writing anonymous methods. Other than that, there is nothing special about them.  

Without much ado, here is a delegate point to an anonymous method defined via a  

            return_string lambda_del = () => 
                Console.WriteLine("this is from the anonymous method written via lambda expressions"); ; 

//here is the delegate delclaration 

public delegate void return_string(); 

If you look closely, the '()' indicates the entire function name. Yep! It looks very odd and looks very weird. Then, you have this '=>' which further makes things look awkward.  

The truth is, it is simply a notation used to information the compiler that a anonymous method is defined. Once you look past the '()' and '=>" you will see that, like any method, you have the flower brackets and standard method code.  

So yes, don’t get freaked out by the weird symbols. When you want to call this anonymous method, you simply call it through the delegate.  

Here is another anonymous method, which accepts parameters and also returns a value.  

            combine_strings lambda_del_return = (x, y) => 
                return (x + y); 
            string lambda_combine_string = lambda_del_return("what is this", "anonymous thingy"); 

//and here is the delegate declaration 

public delegate string combine_strings(string a, string b); 

Here, the compiler will automatically figure out of the types of x and y, based on the delegate declaration. Other than that, this is similar to the earlier lambda explanation and the default method definition behavior.  

Now, before I jump into my analysis of delegates and anonymous methods, wish to add a few thoughts on multi casting. Single casting is when a delegate points to just one method. Multi casting is when the same delegate points to multiple methods.  

A single delegate can point to multiple methods (anonymous or standard). That way, when you call the delegate, it will call all the methods that it is pointing. It is okay to assume that each of them will be called in the sequence they were added to the delegate. Of course, this is not guaranteed. If you want guaranteed sequential delegate based execution, you got to use some extra flags.  

As usual, check out our code repo and see how it works.  

Delegates and Anonymous methods discussion.  

When we enter the world of events and callbacks, we will use a lot of delegates, methods, lambda expressions, anonymous methods and of course casting – single and multiple.  

The benefit I can see with delegates is that they allow you to point at methods. Not just one method, but at several. This means, you can have one 'handle' pointing at several methods. When your methods are actually events, you actually have one 'handle' that is constantly 'watching out' for something to happen at any one of the events it is connected to. This is why, the learning of events has a prerequisite learning of delegates and how to use them.  

Then, there are lambda expressions. Back when we were disussing decision making and program flow, I talked about it is possible to replace conditional operator and null coalescing operator with if else. Yet, we use specialized operators because it saves us time, and it makes the code more efficient. That is one reason why we use lambda expressions.  

Further, there is the question of access. When you define a method with a name, someone, somehow can touch it. However, when you make a method anonymous, you can only access it through the delegate that holds it.  

Before I end this blog post, just want to add that understanding of delegates and anonymous methods are crucial for building top quality software. You better start by upping your game from now one. 

Not that you ever let it go down, now, did you?


One of the nice things about the internet is that, it works even when things are broken. That is because of the 'distributed' nature of its workings, as against the direct nature of working 

For instance, think how you usually give gifts. You go to the store. You buy the gift. You pay for the gift. Then, you get it wrapped up in colorful paper. Then, you hand deliver it to the person of interest yourself. This is a 'direct' or 'non-distributed' way of doing things.  

The opposite of this will go something like this. You buy the gift. You ask your friend to put it in a envelope. That envelope is then sent to the nearest post box. Then, the postman picks it up. Then, it goes to the post office. Then, the letter goes to the delivery van, which will keep transferring the letter from van to van until it reaches the target city. There, the postman will get it, and he or she will then deliver the letter to the person who should get that gift. This is the distributed way of doing things.  

You see, there are some big problems when you do things the 'non-distributed' way. Let's say that on the day you were supposed to give the gift, you become busy. It could be work or some sudden cold or you just plain bored. Since you are the only person, if anything happens to you, the gift becomes undelivered which is sad. 

When you do things through the 'distributed' way, there are so many agents involved. If the post box is full, then you can always find another post box. If the van gets puncture, then post office will get another van. If the postman is not well or on leave from work, another postman will do the delivery. That means, when things go wrong, the 'distributed' system will still do the job you asked it to do.  

However, there is one problem that happens with a distributed system. When you are taking care of buying, packaging and delivery of your own gift – the non-distributed way – there is very little chance that anything can go wrong with your gift. Since the gift never leaves your sight, you can be assured that the person will get the gift they were supposed to get.  

When you use a distributed system, there are so many things that can go wrong. 

The gift may get lost during delivery. 

The postman might replace the gift with something else. For instance, you may have sent a cat as a gift. The postman might remove the cat and put a dog in the box.  

Since there are so many hands who are handling the package, it's impossible to find out who messed up. 

The worst part is, if the gift box contents are changed, the receiver of the gift may never even realize that they got something that they were not supposed to get.   

The internet, for extremely practical reasons, works on the basis of a distributed system. 

That is also what makes the internet so awesome. So awesome that you can have friends from other worlds. So awesome that you can buy things while you are taking care of your business. So awesome that you can see things that you normally cannot see for real.  

The same distributed nature of internet is also what makes it so dangerous. Just like a faulty post man who is changing your cat into a dog, the internet has people who will happily change the contents of what you sent and what was received. The consequences of this can be extremely dangerous.  

So, what is the solution then? Well, just like in the real world, the internet also has some safeguards. If you have ever visited any government offices or educational institution offices, you will see that they have wax and seal. When something confidential needs to sent across, the letter is sealed with a wax. The interesting thing about wax is that, if someone opens it, it will crack open and is impossible to seal back exactly how it was before.  

However, there is a small problem though. Suppose, that the envelope was waxed shut. That means, if someone did open the envelope and try to reseal it, there would be wax marks to indicate tampering. However, what if this person has new envelope and wax? Then, she can simply open the envelope, read the contents, get that new envelope and seal it. Since wax does not appear like it was tampered with, the recipient will assume that the message was not read or modified by anybody.   

This scenario is why critical documents are always waxed shut, and then sealed upon. You see, it is quite easy for someone to buy envelope and wax of the same type that was used for the letter. However, the seal is always kept in a safe location. That means, if someone did use another envelope and wax, they won't be able to put the seal back, exactly as it was. That is the whole point of the seal, and that is idea behind hashing.    

Just like that wax, in computer world, we have something called 'hash'. Hashing works with strings, mostly. The idea behind hashing – just like the wax – is that each unique sentence or sequence can be converted into or hashed into a unique code.  

Suppose, you have a sentence called 'hello'. When you hash it, you get a code, lets say ,'1234'. Now, when that 'hello' reaches the other person on the other side of the internet, he will check the hash of 'hello'. She will get the same '1234'. In case, someone tampered with the 'hello' and turned it into 'goodbye', then the hash code would be something other than '1234'. By comparing the hashes, the recipient will know the message has been tampered. 

Just like the envelope with the wax. If someone has opened the letter, and rewaxed it, there will still be some traces that indicate that the letter has been tampered with.  

In programming, hashing actually is two steps. The first step is to convert the string into its byte stream. Then, run that byte stream through a hashing method. Check out our code repo to see how this is done. 

Task Factory

In an earlier post, we talked about parent tasks and child tasks. We told how the parent-child tasks scenario works when you have a main task which depends on a group of child tasks. This is excellent when you have such a dependent situation. However, every time you create a child task, you have to do add a task as a child to the parent specifically.  

The isuse – even if a minor one – is if you forget to add the child to the parent. That can cause problems. Instead of doing this whole parent child thing, you could just use a factory. That is the Task Factory. When you use a task factory, the child threads get automatically added to the parent. Everything else remains the same.  

As with most things that has to do with tasks, the code for this is pretty extensive. Check out our repo for more details. You know we write a lot of comments, which is almost an extension of this blog.

Making Decisions

In our earlier post, we talked about using boolean operators and boolean expressions. These expressions will return a yes or a no. However, you will have to do something based on the yes and no. This will your program to do some or all of the following.  

  • Choose between two options.  

  • Choose between more than just two options.  

  • Keep circling between a statement depending on some terminating condition.  

  • ...and some combination of the above.  

Stuff like making decisions in programming is best learnt with code. So, we would ask you to check our code repository for this right away.  

However, for those who want a little bit of theory, I have included short snippets of the different decision based things available in c sharp.  

If/else/else if 

This is used when you have to check multiple conditions.  

Let's say, you want to decide how you want to travel to a movie, and this depends on the number of people who are coming.  

If you are going alone, you will take a bus.  

If you are going with a friend, you will take a bike. 

If you are going with two friends, you will book a cab.  


This is used when you want to keep doing the same thing until some expression becomes true or false.  

For example, you will keep staring at that girl in the class until she scolds you.  

For example, you will keep working until your boss says you can go home.  

You will keep watching TV until you fall asleep.  

do while 

This is used when you want to do something exactly one time, and then keep doing it until some expression becomes true or false.  

For example, you will stare at that girl, then keep staring at her until she slaps you. 

For example, you will start working, then continue to work until your boss says you can go home.  

Understanding the difference between While and Do While 

There is a very small difference between while and do while. Let's take the girl staring/slapping example.  

you will keep staring at that girl in the class until she scolds you. (while) 

you will stare at that girl, then keep staring at her until she slaps you. (do while) 

In while, you will check the condition and then start doing something. In this example, if you are using while, there is a good chance that the girl may slap you, even before you have a chance to look at here. In while, if the expression is already active, you won't even get to do the work, even once.  

In do while, no matter what happens, you will at least do something once. That means, you will look at the girl exactly once. This is for sure. Then, after you have looked at her once, she may or may not slap you. If she does not slap you, will keep looking. Then, when she does slap you, you will stop looking.  

Another example would be the television example. If you are using while, you will first check if you are sleepy. If you are sleepy from the beginning itself, then you won't watch TV at all.  

In do while, it does not matter whether you are sleepy or not. You will start watching right away, and then, you will check if you are sleepy. If you are sleepy, then you will stop watching TV. If not, you will continue to watch until you are sleepy.  

for loop 

Ah! The for loop. As a trainer, no other concept in programming has bothered me (with respect to students) as much as the for loop. In fact, students go bonkers just trying to understand how for loop works.  

I think the problem is, many students tend to learn for loop first, then learn while and do while loop. The interesting thing is, a for loop is nothing but a while loop with a counter. When the counter becomes zero (like those bombs in movies), the loop ends.  

That is why, that is all I will talk about for loops. Learn while loop properly, and you learn for loop as well.  


A switch is like a ready made version of if else. Yes, that is all that I am going about that as well.  

null-coalescing operator (try saying this five times, really fast!) 

This is a special type of operator. To explain, this I really have to give a code line sample. Here we go. 

string something_something = something_empty ?? "wow value"; 

This operator is useful when you wish to assign a default value to an object that might be null. This null coalescing operator is kind of a tricky beast. That's because, there are some specific scenarios where you would use this.  

Here, let me give you a scenario. Suppose, you are planning to watch this new batman movie. However, you are not sure if you will be able to get tickets for it, when you reach the multiplex. Then, there is also that new captain america movie. That would be your second choice. What if, both the movies are sold out? Then, you will tell yourself that you will watch some hindi movie that you know for sure is bad, and hence it is a certainty that you will get tickets. 

The null coalescing operator is like that. It will check if your object has a value assigned to it. If it already has a value, then this operator will not do anything. However, if the object is null, it will assign some default value. The idea is to make sure that you don’t do stuff with a null object.  

Yes, this is important because, you cannot do anything with a null object. Imagine drinking coffee from an empty cup. That's what a null object feels like.  

In the above code, string something_something = something_empty ?? "wow value";, something_something will be assigned whatever is stored in something_empty. However, in case, something_empty is actually null, then something_something gets assigned "wow value". 

conditional operator 

similar to the null coalescing operator from earlier, the conditional operator serves a special purpose of assigning a value based on the true/false of a boolean expression 

string you_entered = some_expression ? "you entered true" : "you entered false"; 

In this code, if some_expression is true, you_entered gets assigned "you entered true". If some_expression is false, you_entered gets assigned "you entered false". 

note about conditional operator and null coalescing operator  

When you look below the surface of these two operators, you will notice that they are not really doing anything special. The same thing that a conditional operator does can be done with a simple if/else. The task that null coalescing operator does can also be done with a if/else.  

Then, why bother using them, would be the question? 

I, personally, prefer simpler solutions. Given a choice, I would just if/else over these two operators. However, there are at least two reasons to use them.  

First reason is compiler efficiency. While you are practicing programming, you don’t normally worry about things like optimization. For instance, you should not worry about deciding between using a string object or a string builder object. At least, not while you are learning to code just now. However, when you graduate to building real world applications, every tiny code that can be saved, should be saved.  

Even a simple if/else uses 4 lines of code. These operators do the same thing in just one line of code. That's a 75 % efficiency, in terms of lines of code written alone. This helps the compiler to build things faster, make the final executable smaller and also, help make debug easier.  

The second reason, is that, it just looks cool. If you are using just if/else everywhere, then you are but a simpleton. You use all these cool and complicated operators, suddenly people you think you are a Jedi or a Sith, depending on your force orientation.  

Of course, check out our code repository to understand these better 

Parallel programming

When you are working with threads, there are a couple of things you are trying to do. One of those things is that you are trying to squeeze the most out of your CPU. As I told you in an earlier post, CPUs are fast and with proper thread programming, we can get more out of them.  

However, what happens when you have a CPU that has more than one core? Today, almost all processors are multi-core. If you have a 4 core processor, you can do things parallelly. That way, on top of utilizing the most of each core, you are also using all the cores to the greatest extent. That is what the Parallel class provides for.  

Of course, our repo shows how to use the Parallel class.  

Before you launch into a hurrah, there is something to think about here. Utilizing threads itself takes a lot of effort. You need to plan carefully to actually make the most of it. Like it says in the spiderman movie, with great power comes great responsibility. This responsibility is further magnified when you are working with Parallel programming.   

First off, the work you need to do has to parallel in nature to begin with. For instance, if you are writing a software that manages the ink levels of 4 different printers with their own separate ink supply, it is ideal for parallel programming. Each printer and its ink is independent of each other. However, if a single ink supply is being distributed across all the printers, that is not a case for parallel programming at all. That means, if you 'force' a parallel programming design on a clearly non-parallel scenario, it is going to be pretty bad.  

Don’t' use parallel programming because you want to. Use it when you have to. In fact, this applies to programming in general. Don’t use some cool new feature because the new feature is there. Use it because it really helps you. 

Program Flow

Anytime you do anything, it boils down to decisions. Without an option to make decisions, things would remain where they are. Or, they would do the same thing everyday, irrespective of what is happening in the outside world. If I am sounding like some big time advisor, it is because, program flow and hence decisions are an essential part of any software design.  

Before we get into the software part, look at how everyday works. 

Consider something as simple as travelling from home to college. If you woke up early that day, you would decide to take a longer shower, longer breakfast, take that long road that has that Women's college...If you woke up, you will probably not shower at all, forget about that breakfast and may be decide to take the shorter route. There are a couple of interesting things happening here.  

First up is the data that decides the follow up actions. In this case, the chief decision making data seems to be 'waking up' and 'available time'. The earlier you wake up, the more time you will have. The later you wake up, the less time you will have. Depending on the amount of time you have (which is inexplicably linked to how early you woke up), you will decide to do one thing or two things or many things. 

Let's say, shower takes 30 minutes, break fast takes 30 minutes and that Women's college takes one hour. Total, you need two hours to do everything. Let's say that your college is at 9 AM. 

Suppose you woke up at 6 AM. You now have three hours. You need two hours to do everything, so you will do all of them.  

Suppose you woke up at 7 AM. You now have two hours. This is still enough for you to do all the above tasks.  

Suppose you woke up at 8 AM. You now have one hour. You have two hours worth of tasks, but only one hour to spend. You start doing what is called is prioritizing. Obviously, the Women's college thing is important. So, you decide to not shower or have breakfast.  

Suppose you woke up at 8 : 30 AM. Now, the Women's college thing is ruled out. You can either choose to shower or breakfast.  

Like this, there could any combination of decisions that you would have to make. Your mind automatically builds a decision tree and your personality will eventually decide what happens next.  

A software or project or app or application that you plan to build is essentially a decision tree. Any decision, real life or computer, will always have at least two components. First would be the data that is used to make decisions. Then, some way of 'comparing' this data (like when you compared the available time with available tasks in the above example), and using the results of that comparison for the actions that follow.  

That is what is called as program flow. Data and data comparison. Of course, there are other factors at play here. For instance, when you have one hour only (in the above example) you have a choice to decide between Women's college or breakfast and shower. Here, you would assign some priority factor, and decide which task gets done.  

Further, the results of the data comparison could be more data, that does not lead to a decision. Rather, it could lead to another comparison.  

Then, there is the nature of data. The data itself may come from multiple sources. In a computer program, the data may come from the user. When you go to the ATM, the PIN number is something that you enter. The day may come from something else. When you swipe your card at the ATM, the ATM will automatically read all the necessary information from the card itself. The data may come from a server. Once you have entered the right PIN, the ATM will then contact your bank to check your balance and provide you with banking services.  

In real life, you, as a person collect data through your senses, like eye, for example. Then, you process the data in your brain and your brain takes care of decisions.  

In a program, what you have are types (this has already been extensively discussed in our earlier blog posts) that allow you to store data. Then, you have boolean operators (that help you form boolean expressions) which allow you to make yes/no decisions. As you can see, programming is just like real life. We just replace the things that we naturally do in our brain with programming things.  

Now, a full discussion about booleans and expressions is out of the scope of our blog post. But of course, we won't leave you high and dry. Check out our linked repository to know more about the booleans and how they work. 

Parallel Query and Aggregate Exception

Remember, back when we wrong about parallel programming. The same thing happens with running queries against data as well. If it is possible to divide a queries parallel (with the same rules that I have explained in the earlier blog post), then you can use the parallel query option.  

It would look something like this. 

list_of_numbers.AsParallel().Where(x => temp(x)); 

You will notice that above syntax is similar to LINQ queries that you do. Of course, this one is made to work parallely 

Then, there is this thing about Aggregate Exception.  

Exceptions (you will read more about them later) are raised when something goes wrong, fatally. Normally, the application would quit. That would be, of course, not cool at all. That is why you get to catch exceptions. However, what happens when you are working with hundreds of transactions, like writing hundreds of records into a database.  

When that happens, you can always assume that at least some of them will go bad. However, it does not make sense to stop the application at that instant. However, it would be nice if you catch the exceptions together, and do something with it. For instance, you could display an output or write a log that says that so many exceptions (or write errors) happened.  

This is how you would catch the Aggregate exception. 

catch(AggregateException e) 

Use the objecte and do what you want with it. Find out the total exceptions, look inside and see what all exceptions were thrown and so on.  

As always, our code repo has a sample that you can happily use. 

Locks and Deadlocks

Before we understand locks, we will have to understand the scenario when locks are used.  

We have previously blogged about threads and of course multithreaded stuff. Multithreading is good but it also problems. Imagine a file that is being written into simultaneously by two methods. At some point, it could happen so that one method is waiting for the other method to finish. Meanwhile, the second method is waiting for the other method to finish writing into the file.  

Scenarios like this happen all the time. The server is waiting for the client while on the other side, the client is waiting for the server.  

In fact, this happens a lot in real life too. Mother is waiting for son to apologize. Son is waiting for mother to apologize. Boyfriend is waiting for girlfriend to call. Girlfriend is waiting for boyfriend to call. Deadlocks! 

The source of the deadlock is almost always some resource, like a file that is being simultaneously written into. Think of that old story, where there is a single bridge and two girls start to cross it from both sides. Neither of them wants to budge back and now they meet in the center. That means, they are both stuck and no one gets to cross the road. Further, others who are trying to cross the bridge are also stuck.  

A 'lock' does the most simple thing. It makes sure that if a resource (like a file) is currently being used by a method, others won't get it. To go back to the bridge example, the moment a person steps on the bridge, the other entry is blocked for sure.  This way, at any point of time, the single bridge is used by one person and one person only.  

That is precisely what locks do in a c sharp programming. They work with threads, of course. If a thread is accessing an element and has lock on it, other threads will simply wait. That way, deadlocks are avoided.  

As always, check our code repo to see how locks work.