Skip to content

Commit

Permalink
use code includes over inline copies (dotnet#5600)
Browse files Browse the repository at this point in the history
* use code includes over inline copies

Fixes dotnet#887

Relies on dotnet/samples#80

Provide links to downloadable samples, and update incline samples to use include snippets from the sample.

* fix build errors

A couple tags were mis-typed

* respond to feedback

Reviews from @JRAlexander and @repetrusha

* fix one last typo
  • Loading branch information
BillWagner authored Jun 6, 2018
1 parent 3f477b5 commit 3618803
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 239 deletions.
6 changes: 2 additions & 4 deletions docs/csharp/delegate-class.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,8 @@ Suppose you wanted to sort a list of strings by their length. Your
comparison function might be the following:

```csharp
private static int CompareLength(string left, string right)
{
return left.Length.CompareTo(right.Length);
}
private static int CompareLength(string left, string right) =>
left.Length.CompareTo(right.Length);
```

The method is declared as a private method. That's fine. You may not
Expand Down
1 change: 1 addition & 0 deletions docs/csharp/delegates-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ This topic will be covered under the following articles:

This article discusses how you should distinguish between using events and delegates in your designs.

You can download the [delegates sample](https://github.com/dotnet/samples/tree/master/csharp/delegates-and-events) and the [events sample](https://github.com/dotnet/samples/tree/master/csharp/events) from our GitHub samples repository.
102 changes: 10 additions & 92 deletions docs/csharp/delegates-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,35 +78,18 @@ Let's start small: the initial implementation will accept new messages,
and write them using any attached delegate. You can start with one delegate
that writes messages to the console.

```csharp
public static class Logger
{
public static Action<string> WriteMessage;

public static void LogMessage(string msg)
{
WriteMessage(msg);
}
}
```
[!code-csharp[LoggerImplementation](../../samples/csharp/delegates-and-events/Logger.cs#FirstImplementation "A first Logger implementation.")]

The static class above is the simplest thing that can work. We need to
write the single implementation for the method that writes messages
to the console:

```csharp
public static void LogToConsole(string message)
{
Console.Error.WriteLine(message);
}
```
[!code-csharp[LogToConsole](../../samples/csharp/delegates-and-events/Program.cs#LogToConsole "A Console logger.")]

Finally, you need to hook up the delegate by attaching it to
the WriteMessage delegate declared in the logger:

```csharp
Logger.WriteMessage += LogToConsole;
```
[!code-csharp[ConnectDelegate](../../samples/csharp/delegates-and-events/Program.cs#ConnectDelegate "Connect to the delegate")]

## Practices

Expand All @@ -130,50 +113,14 @@ creating other logging mechanisms.
Next, let's add a few arguments to the `LogMessage()` method so that
your log class creates more structured messages:

```csharp
// Logger implementation two
public enum Severity
{
Verbose,
Trace,
Information,
Warning,
Error,
Critical
}

public static class Logger
{
public static Action<string> WriteMessage;

public static void LogMessage(Severity s, string component, string msg)
{
var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
WriteMessage(outputMsg);
}
}
```
[!code-csharp[Severity](../../samples/csharp/delegates-and-events/Logger.cs#Severity "Define severities")]
[!code-csharp[NextLogger](../../samples/csharp/delegates-and-events/Logger.cs#LoggerTwo "Refine the Logger")]

Next, let's make use of that `Severity` argument to filter the messages
that are sent to the log's output.

```csharp
public static class Logger
{
public static Action<string> WriteMessage;

public static Severity LogLevel {get;set;} = Severity.Warning;

public static void LogMessage(Severity s, string component, string msg)
{
if (s < LogLevel)
return;

var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
WriteMessage(outputMsg);
}
}
```
[!code-csharp[FinalLogger](../../samples/csharp/delegates-and-events/Logger.cs#LoggerFinal "Finish the Logger")]

## Practices

You've added new features to the logging infrastructure. Because
Expand All @@ -198,42 +145,13 @@ each message is generated.

Here is that file based logger:

```csharp
public class FileLogger
{
private readonly string logPath;
public FileLogger(string path)
{
logPath = path;
Logger.WriteMessage += LogMessage;
}

public void DetachLog() => Logger.WriteMessage -= LogMessage;

// make sure this can't throw.
private void LogMessage(string msg)
{
try {
using (var log = File.AppendText(logPath))
{
log.WriteLine(msg);
log.Flush();
}
} catch (Exception e)
{
// Hmm. Not sure what to do.
// Logging is failing...
}
}
}
```
[!code-csharp[FileLogger](../../samples/csharp/delegates-and-events/FileLogger.cs#FileLogger "Log to files")]


Once you've created this class, you can instantiate it and it attaches
its LogMessage method to the Logger component:

```csharp
var file = new FileLogger("log.txt");
```
[!code-csharp[FileLogger](../../samples/csharp/delegates-and-events/Program.cs#FileLogger "Log to files")]

These two are not mutually exclusive. You could attach both log
methods and generate messages to the console and a file:
Expand Down
142 changes: 18 additions & 124 deletions docs/csharp/event-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,7 @@ robust algorithms.
Here is the initial event argument declaration for finding a sought
file:

```csharp
public class FileFoundArgs : EventArgs
{
public string FoundFile { get; }

public FileFoundArgs(string fileName)
{
FoundFile = fileName;
}
}
```
[!code-csharp[EventArgs](../../samples/csharp/events/Program.cs#EventArgsV1 "Define event arguments")]

Even though this type looks like a small, data-only type, you should
follow the convention and make it a reference (`class`) type. That
Expand All @@ -87,48 +77,27 @@ specialization.
Let's fill out the FileSearcher class to search for files that match
a pattern, and raise the correct event when a match is discovered.

```csharp
public class FileSearcher
{
public event EventHandler<FileFoundArgs> FileFound;

public void Search(string directory, string searchPattern)
{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
FileFound?.Invoke(this, new FileFoundArgs(file));
}
}
}
```
[!code-csharp[FileSearxcher](../../samples/csharp/events/Program.cs#FileSearcherV1 "Create the initial file searcher")]

## Definining and Raising Field-Like Events

The simplest way to add an event to your class is to declare that
event as a public field, as in the above example:
event as a public field, as in the preceding example:

```csharp
public event EventHandler<FileFoundArgs> FileFound;
```
[!code-csharp[DeclareEvent](../../samples/csharp/events/Program.cs#DeclareEvent "Declare the file found event")]

This looks like it's declaring a public field, which would appear to
be bad object oriented practice. You want to protect data access
be bad object-oriented practice. You want to protect data access
through properties, or methods. While this make look like a bad
practice, the code generated by the compiler does create wrappers so
that the event objects can only be accessed in safe ways. The only
operations available on a field-like event are add handler:

```csharp
EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>
Console.WriteLine(eventArgs.FoundFile);
lister.FileFound += onFileFound;
```
[!code-csharp[DeclareEventHandler](../../samples/csharp/events/Program.cs#DeclareEventHandler "Declare the file found event handler")]

and remove handler:

```csharp
lister.FileFound -= onFileFound;
```
[!code-csharp[RemoveEventHandler](../../samples/csharp/events/Program.cs#RemoveHandler "Remove the event handler")]

Note that there's a local variable for the handler. If you used
the body of the lambda, the remove would not work correctly. It would
Expand Down Expand Up @@ -171,24 +140,12 @@ have seen the event. If there are no subscribers, the field would
indicate a cancel incorrectly.

Let's implement the first version for this sample. You need to add a
boolean field to the FileFoundEventArgs type:
boolean field named `CancelRequested` to the `FileFoundArgs` type:

```csharp
public class FileFoundArgs : EventArgs
{
public string FoundFile { get; }
public bool CancelRequested { get; set; }
[!code-csharp[EventArgs](../../samples/csharp/events/Program.cs#EventArgs "Update event arguments")]

public FileFoundArgs(string fileName)
{
FoundFile = fileName;
}
}
```

This new Field should be initialized to false, so you don't cancel
for no reason. That is the default value for a boolean field, so that
happens automatically. The only other change to the component is to
This new Field is automatically initialized to `false`, the default value for a Boolean field, so you don't cancel
accidentally. The only other change to the component is to
check the flag after raising the event to see if any of the
subscribers have requested a cancellation:

Expand All @@ -206,7 +163,7 @@ public void List(string directory, string searchPattern)
```

One advantage of this pattern is that it isn't a breaking change.
None of the subscribers requested a cancel before, and they still are
None of the subscribers requested a cancellation before, and they still are
not. None of the subscriber code needs updating unless they want to
support the new cancel protocol. It's very loosely coupled.

Expand Down Expand Up @@ -237,41 +194,20 @@ can also make the types used for the arguments internal as well.
You'll start by creating the new EventArgs derived class for
reporting the new directory and progress.

```csharp
internal class SearchDirectoryArgs : EventArgs
{
internal string CurrentSearchDirectory { get; }
internal int TotalDirs { get; }
internal int CompletedDirs { get; }

internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs)
{
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
}
}
```
[!code-csharp[DirEventArgs](../../samples/csharp/events/Program.cs#SearchDirEventArgs "Define search directory event arguments")]

Again, you can follow the recommendations to make an immutable
reference type for the event arguments.

Next, define the event. This time, you'll use a different syntax. In
addition to using the field syntax, you can explicitly create the
property, with add and remove handlers. In this sample, you won't
need extra code in those handlers in this project, but this shows how
need extra code in those handlers, but this shows how
you would create them.

```csharp
internal event EventHandler<SearchDirectoryArgs> DirectoryChanged
{
add { directoryChanged += value; }
remove { directoryChanged -= value; }
}
private EventHandler<SearchDirectoryArgs> directoryChanged;
```
[!code-csharp[Declare event with add and remove handlers](../../samples/csharp/events/Program.cs#DeclareSearchEvent "Declare the event with add and remove handlers")]

In may ways, the code you write here mirrors the code the compiler
In many ways, the code you write here mirrors the code the compiler
generates for the field event definitions you've seen earlier. You
create the event using syntax very similar to that used for
[properties](properties.md). Notice that the handlers have different
Expand All @@ -285,43 +221,7 @@ subdirectories and raises both events. The easiest way to accomplish
this is to use a default argument to specify that you want to search
all directories:

```csharp
public void Search(string directory, string searchPattern, bool searchSubDirs = false)
{
if (searchSubDirs)
{
var allDirectories = Directory.GetDirectories(directory, "*.*", SearchOption.AllDirectories);
var completedDirs = 0;
var totalDirs = allDirectories.Length + 1;
foreach (var dir in allDirectories)
{
directoryChanged?.Invoke(this,
new SearchDirectoryArgs(dir, totalDirs, completedDirs++));
// Recursively search this child directory:
SearchDirectory(dir, searchPattern);
}
// Include the Current Directory:
directoryChanged?.Invoke(this,
new SearchDirectoryArgs(directory, totalDirs, completedDirs++));
SearchDirectory(directory, searchPattern);
}
else
{
SearchDirectory(directory, searchPattern);
}
}

private void SearchDirectory(string directory, string searchPattern)
{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
var args = new FileFoundArgs(file);
FileFound?.Invoke(this, args);
if (args.CancelRequested)
break;
}
}
```
[!code-csharp[SearchImplementation](../../samples/csharp/events/Program.cs#FinalImplementation "Implementation to search directories")]

At this point, you can run the application calling the overload for
searching all sub-directories. There are no subscribers on the new
Expand All @@ -331,13 +231,7 @@ that this works correctly.
Let's add a handler to write a line that shows the progress in the
console window.

```csharp
lister.DirectoryChanged += (sender, eventArgs) =>
{
Console.Write($"Entering '{eventArgs.CurrentSearchDirectory}'.");
Console.WriteLine($" {eventArgs.CompletedDirs} of {eventArgs.TotalDirs} completed...");
};
```
[!code-csharp[Search](../../samples/csharp/events/Program.cs#Search "Declare event handler")]

You've seen patterns that are followed throughout the .NET ecosystem.
By learning these patterns and conventions, you'll be writing
Expand Down
Loading

0 comments on commit 3618803

Please sign in to comment.