1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Diagnostics;
5using System.Globalization;
6using System.IO;
7using System.IO.Compression;
8using System.Linq;
9using System.Linq.Expressions;
10using System.Reflection;
11using System.Security;
12using System.Security.Cryptography;
13using System.Security.Permissions;
14using System.Security.Policy;
15using System.Text;
16using System.Text.RegularExpressions;
17using System.Threading;
18using System.Xml;
19
20namespace Moserware.BetterKnowAFramework
21{
22 static class Program
23 {
24 static void Main(string[] args)
25 {
26 #region About me ...
27
28 // Jeff Moser
29 // jeff@moserware.com
30 // My Blog: http://www.moserware.com/
31 #endregion
32
33 // I gave this talk at Indy Code Camp on Saturday, April 26, 2008
34 // The format was just stepping through this file by hitting F11
35 // in the debugger and discussing things as they came up.
36 #region ...
37
38 // Poll
39 // How many of you have used:
40 // 1. System.Text.StringBuilder
41 // 2. System.IO.Path
42 // 3. System.Text.RegularExpressions.Regex
43 // 4. System.IFormattable
44 // 5. System.AppDomain
45 // 6. System.Linq.Expressions.Expression
46
47 // Poll results: mostly 1 & 2, some 3, couple of 5.
48 #endregion
49 #region Startup Things (a.k.a. "How did we get here?") ...
50
51 // Your OS starts a process:
52 // System.Diagnostics.Process.Start()
53
54 // Then a thread gets queued up:
55 // System.Threading.Thread.Start()
56
57 // Now, the CLR will execute your assembly:
58 // System.AppDomain.ExecuteAssembly()
59 // System.AppDomain._nExecuteAssembly (internal call)
60
61 // It does this by loading your code and finding the
62 // static method marked with ".entrypoint" in IL
63
64 System.Diagnostics.Process myProcess = Process.GetCurrentProcess();
65 AppDomain myAppDomain = AppDomain.CurrentDomain;
66
67 // I'll be using C# 3's type-inference from now on by
68 // using "var". It's just as if I typed everything out:
69 var myAssembly = Assembly.GetEntryAssembly();
70 var myThread = Thread.CurrentThread;
71
72 var greeting = "Hello World!";
73 #endregion
74 #region Beginner ...
75
76 WarmupClasses();
77 ExploringSystemCollectionsGeneric();
78 FunWithStrings();
79 BoolVsEnumDebate();
80 #endregion
81 #region Intermediate ...
82
83 ILoveRegularExpressions();
84 OverviewOfStreams();
85 DabblingWithSecurity();
86 HowDoWeRelate();
87 PeeringIntoIDisposable();
88 AggregatesMakeMortHappy();
89 // GAC -- look in the virtual C:\windows\assembly folder
90 #endregion
91 #region A bit more challenging ...
92
93
94 UnderstandingHowLinqIsImplemented();
95 DoesYourCodePassTheTurkeyTest();
96 SewingWithThreads();
97 EventsCanBeTricky();
98 BriefCoverageOfTracing();
99 InterestingInternalClasses();
100
101 // Juval's C# Coding Conventions
102 // http://www.idesign.net/idesign/download/IDesign%20CSharp%20Coding%20Standard.zip
103
104 // Recommend reading "Framework Design Guidelines" by Cwalina and Abrams
105 // digest at: http://blogs.msdn.com/kcwalina/archive/2008/04/09/FDGDigest.aspx
106 // (new updated edition coming out later this year)
107 // naming: callback, canceled, Email, FileName, HashTable, Id id, Indexes, Ok, ok, Pi, signIn, userName, whitespace, writable vs writeable
108
109 // Expert .NET 2.0 IL Assembler by Serge Lidin
110
111 // Obscure classes, but helpful
112 // System.WeakReference
113 // System.ComponentModel.ISynchronizeInvoke
114 #endregion
115
116 // Questions?
117 }
118
119 private static void WarmupClasses()
120 {
121 #region System.IO.Path ...
122
123 // Very useful class I didn't know about for awhile
124 // Helps manipulate file paths
125
126 var sampleFileName = @"C:\Windows\System32\calc.exe";
127 var sampleDirectory = Path.GetDirectoryName(sampleFileName);
128 var isPathRootedYes = Path.IsPathRooted(sampleFileName);
129 var isPathRootedNo = Path.IsPathRooted("helloworld.exe");
130 var calcexe = Path.GetFileName(sampleFileName);
131 var otherFileInDirectory = Path.Combine(sampleDirectory, "cmd.exe");
132 #endregion
133 #region System.Math (Show #293) ...
134
135 // Convenient math functions
136
137 var max = Math.Max(4, 2);
138 var min = Math.Min(4, 2);
139 var abs = Math.Abs(-42);
140 var tan = Math.Tan(Math.PI / 4);
141 var floor = Math.Floor(4.2);
142 var sin = Math.Sin(Math.PI / 4);
143
144 // This one returns the full 64 bit result
145 var bigMul = Math.BigMul(int.MaxValue, int.MaxValue);
146 #endregion
147 #region System.Console (Show #268) ...
148
149 var capsLockOn = Console.CapsLock;
150 var numberLockOn = Console.NumberLock;
151 Console.Title = "Better Know a Framework";
152 Console.BackgroundColor = ConsoleColor.Blue;
153 Console.ForegroundColor = ConsoleColor.Yellow;
154 Console.Clear();
155 Console.WriteLine("Hello World!");
156
157 Console.BackgroundColor = ConsoleColor.Black;
158 Console.ForegroundColor = ConsoleColor.Gray;
159 Console.Clear();
160 #endregion
161 #region Convert (Show #280) ...
162
163 var trueIs1point0 = Convert.ToDouble(true);
164 var goingBeyondInt32 = Convert.ToUInt32(int.MaxValue) + 1;
165
166 var base64Example = Convert.ToBase64String(BitConverter.GetBytes(0xDEADBEEF));
167 var returnTrip = Convert.FromBase64String(base64Example);
168 #endregion
169 #region System.Xml.XmlConvert ...
170
171 var xmlDate = XmlConvert.ToString(DateTime.Now);
172 var traditionalDate = DateTime.Now.ToString();
173 var xmlTrue = XmlConvert.ToBoolean("1")
174 &&
175 XmlConvert.ToBoolean("true");
176 #endregion
177 #region System.Environment (Show #269) ...
178
179 var userName = Environment.UserName;
180
181 // The following method is very useful because these "special folders" can
182 // be in many different spots depending on the OS and language
183 var programFilesFolder = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
184 #endregion
185 #region System.TimeSpan (Show #294) ...
186
187
188 DateTime thirtyMinutesFromNow = DateTime.Now + TimeSpan.FromMinutes(30);
189 #endregion
190
191 }
192
193 private static void FunWithStrings()
194 {
195 #region System.Text.StringBuilder (Show #249) ...
196
197
198 // If you are concatenating several strings together, this is bad:
199 var hello = "Hello";
200 var space = " ";
201 var world = "World!";
202 var badHelloWorld = hello + space + world; // (hello + space) + world
203
204 // The reason is that everytime you concat two things, a temporary
205 // intermediate object is created.
206
207 // Here's the preferred way:
208
209 var sb = new System.Text.StringBuilder();
210 sb.Append("Hello");
211 sb.Append(" ");
212 sb.Append("World!");
213 var helloWorld = sb.ToString();
214
215 // Someone in the audience asked when you should start caring about
216 // the inefficiencies. I said if you know you're going to be concatenating
217 // more than 5 strings than prefer StringBuilder
218 #endregion
219 #region String formatting and IFormattable (Show #315) ...
220
221
222 // note that MyNumber is my custom class that implments IFormattable
223 var currentYear = new MyNumber(DateTime.Now.Year);
224
225 // Here we're showing off the custom formatting.
226 // Note that "roman" is my own format.
227
228 string formattedGreeting
229 = string.Format(@"Hello! My name is {0} and today is {1:D}." +
230 "The year is {2:0,0} ({2:roman})",
231 "Jeff", DateTime.Now, currentYear);
232
233 // Here is the type of thing string.Format is calling under the covers
234 string year1999 = (new MyNumber(1999)).ToString("roman", CultureInfo.CurrentCulture);
235
236 string directoryService = string.Format("{0:(###) ###-####}", 8005551212);
237
238 // More string formatting examples at
239 // http://blog.stevex.net/index.php/string-formatting-in-csharp/
240 #endregion
241 }
242
243 private static void BoolVsEnumDebate()
244 {
245 // Which is easier to understand?
246
247 var exhibitA = string.Compare("Jeff", "jeff", true) == 0;
248 var exhibitB = string.Compare("Jeff", "jeff", StringComparison.CurrentCultureIgnoreCase) == 0;
249
250 // If you're designing public APIs, tend to prefer enums (e.g. StringComparison) over booleans.
251 // It just makes things easier to read.
252
253 }
254
255 private static void ILoveRegularExpressions()
256 {
257 // Here we're only going to show a few numbers. But just imagine
258 // If you had to process thousands or even millions.
259 string phoneNumbersOldFormat = "(800) 555-1212, (317) 222-1234, 765-4951234";
260 var newFormat = Regex.Replace(phoneNumbersOldFormat, @"[^\d,]*
261 (?<areaCode>\d{3})
262 \D*
263 (?<first3>\d{3})
264 \D*
265 (?<last4>\d{4})
266 [^,]*",
267 "${areaCode}.${first3}.${last4} ", RegexOptions.IgnorePatternWhitespace);
268
269 var isSsn = Regex.IsMatch("123-45-6789", @"\d{3}-\d{2}-\d{4}");
270 // many more possibilities
271 // see http://msdn2.microsoft.com/en-us/library/hs600312.aspx
272 }
273
274 private static void OverviewOfStreams()
275 {
276 // Streams are a generic concept in .net
277 // They're very swappable and connectable sort of like Legos.
278
279 // Let's create a stream that writes text, then compresses it,
280 // then encrypts it, then stores it in memory.
281
282
283 var memory = new MemoryStream();
284 var secretKey = "My Secret Password!";
285 var salt = new byte[32];
286 var iv = new byte[16];
287 var passwordBytes = new Rfc2898DeriveBytes(secretKey, salt);
288 var encryptionAlgorithm = Aes.Create().CreateEncryptor(passwordBytes.GetBytes(32), iv);
289
290 var encryption = new CryptoStream(memory, encryptionAlgorithm, CryptoStreamMode.Write);
291
292 var compression = new GZipStream(encryption, CompressionMode.Compress);
293
294 var writer = new StreamWriter(compression);
295
296 // The pipeline/stream is
297 // writer => compression => encryption => memory
298 // Question: Why would this be a bad idea?
299 // writer => encryption => compression => memory
300
301 // As I mentioned in the talk and people in the audience got,
302 // you wouldn't want to do the latter because encrypted data
303 // should be random and won't compress much.
304
305 // Quick way to generate "Hello" repeated 500 times.
306 var input = string.Join(" ", Enumerable.Repeat("Hello", 500).ToArray());
307 var inputSize = input.Length;
308 writer.Write(input);
309 writer.Flush();
310 var outputBytes = memory.ToArray();
311
312 // Write the file from the MemoryStream to disk:
313
314 var temporaryFileName = Path.GetTempFileName();
315
316 using (var outputStream = File.OpenWrite(temporaryFileName))
317 {
318 memory.WriteTo(outputStream);
319 }
320
321 memory.Close();
322
323 // No time to cover IsolateStorageStream, but it's useful.
324
325 }
326
327 private static void AggregatesMakeMortHappy()
328 {
329 // Aggregate Pattern
330 // See "Framework Design Guidelines" pages 240-243
331
332 // Mort is someone who wants to get things done.
333 // See: http://www.codinghorror.com/blog/archives/001004.html
334
335 // Mort likes 1 - 2 lines of code to get things done:
336 // var fileContents = File.ReadAllBytes(path)
337 // var wc = new System.Net.WebClient();
338 // var googlePage = wc.DownloadString("http://www.google.com/");
339 }
340
341 private static void DabblingWithSecurity()
342 {
343 #region System.Security.Cryptography.RandomNumberGenerator (Show #244) ...
344
345 // Question: Why not use System.Random for generating keys?
346 // Answer: It can be a source of easy attack:
347 // http://en.wikipedia.org/wiki/Random_number_generator_attack
348
349 var rng = RandomNumberGenerator.Create();
350 var myRandomBytes = new byte[10];
351 rng.GetBytes(myRandomBytes);
352 rng.GetBytes(myRandomBytes);
353 #endregion
354 #region System.Security.Cryptography.ProtectedData ...
355
356
357 // Here's a quick way of storing secrets in a way that only the current
358 // user can decrypt:
359 var pd = ProtectedData.Protect(ASCIIEncoding.ASCII.GetBytes("Hello World"), null, DataProtectionScope.CurrentUser);
360 var pdString = Convert.ToBase64String(pd);
361 var unprotected = ASCIIEncoding.ASCII.GetString(ProtectedData.Unprotect(pd, null, DataProtectionScope.CurrentUser));
362 #endregion
363 }
364
365
366 private static void InterestingInternalClasses()
367 {
368 // Didn't have time to cover this, but check out:
369 // see http://www.moserware.com/2008/01/borrowing-ideas-from-3-interesting.html
370 // System.Linq.Strings
371 // System.Linq.Error
372 // Microsoft.Contract.Contracts
373 }
374
375 private static void HowDoWeRelate()
376 {
377 #region System.IComparable (Show #305) ...
378
379
380 // You can completely change how your class is sorted/compared.
381 // Note that "MyNumber" class implements IComparable and its
382 // behavior is to sort in descending order.
383 var list = new List<MyNumber>();
384 list.Add(new MyNumber(5));
385 list.Add(new MyNumber(1));
386 list.Add(new MyNumber(3));
387 list.Add(new MyNumber(2));
388 list.Sort();
389 #endregion
390
391 // I didn't cover this but:
392 // If just want equality, override object.Equals(obj)
393 // Question: Why might you want to implement IEquatable?
394 // Answer: It helps value types not box
395
396 // By the way, if you overload Equals, you should overload GetHashCode()
397 // or else things that depend on hash codes (like Dictionary) will
398 // cause confusing results.
399
400 }
401
402 private static void DoesYourCodePassTheTurkeyTest()
403 {
404 // I gave a brief overview of this post:
405 // http://www.moserware.com/2008/02/does-your-code-pass-turkey-test.html
406
407 Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
408 DateTime julyFourthA = DateTime.Parse("07/04/2008");
409 DateTime julyFourthB = DateTime.Parse("07/04/2008", DateTimeFormatInfo.InvariantInfo);
410
411 double fourPointFiveA = double.Parse("4.5");
412 double fourPointFiveB = double.Parse("4.5", NumberFormatInfo.InvariantInfo);
413
414 string fileUpperA = "file".ToUpper();
415 string fileUpperB = "file".ToUpperInvariant();
416 bool wacky = string.Equals(fileUpperA, fileUpperB);
417
418 bool sampleCompare = "File".Equals(fileUpperB, StringComparison.OrdinalIgnoreCase);
419 }
420
421 private static void SewingWithThreads()
422 {
423 #region Threading ...
424
425 // Simple class to do things in the background
426 var bgw = new System.ComponentModel.BackgroundWorker();
427
428 // Threads can be expensive to create. So it's better to have a "pool"
429 // of threads that you can use and then have go to sleep:
430 ThreadPool.QueueUserWorkItem(
431 new WaitCallback(
432 delegate(object args)
433 {
434 for (int i = 0; i < 10; i++)
435 {
436 Thread.Sleep(1000);
437 }
438 }));
439
440
441 var domainNameToIP = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
442
443 object sync = new object();
444
445 // Imagine that you had a naive DNS server that locked every time
446 // it touched the dictionary (including reads)
447
448 lock (sync)
449 {
450 domainNameToIP["Moserware.com"] = "72.3.2.1";
451 }
452
453 // Question: Anything bad about this?
454 // Answer: YES! Very inefficient. Don't need to lock out
455 // people unless a writer occurs. It's better to use
456 // a ReaderWriterLock (or ReaderWriterLockSlim) instead.
457
458 var rwl = new ReaderWriterLock();
459 try
460 {
461 rwl.AcquireWriterLock(Timeout.Infinite);
462 domainNameToIP["moserware.com"] = "72.14.207.121";
463 }
464 finally
465 {
466 rwl.ReleaseWriterLock();
467 }
468
469
470 // no time to cover semaphore, autoresetevent, manualresetevent, waithandle, waitall
471
472 //int myNum = 1;
473 //Interlocked.Increment(ref myNum);
474
475 // Future:
476 // Parallel.For(0, 1000000, i=>
477 // {
478 // result[i] = ReallyExpensiveFunction(i);
479 // }
480
481 // automatically scales to multiple cores.
482 // See http://msdn2.microsoft.com/en-us/magazine/cc163340.aspx
483 #endregion
484 }
485
486 private static void EventsCanBeTricky()
487 {
488 var c = new SampleEventRaisingClass();
489 c.SomethingHappened += (s, e) => Console.WriteLine("Something Happened");
490 c.OnSomethingHappened();
491 }
492
493 private static void BriefCoverageOfTracing()
494 {
495 #region Shows #333 and #334 ...
496
497
498 // printf all grown up
499 // System.Diagnostics
500 Trace.Listeners.Add(new TextWriterTraceListener(Path.GetTempFileName()));
501 Trace.WriteLine("Hello from BriefCoverageOfTracing");
502
503 // Can be used for diagnosing bugs on customers machines if you have Trace logs.
504 #endregion
505
506 }
507
508 private static void UnderstandingHowLinqIsImplemented()
509 {
510