Description: BetterKnowAFramework
Link: http://www.codekeep.net/snippets/20cce117-3629-47dd-a758-ee58e6b8feae.aspx
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Security.Policy;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
namespace Moserware.BetterKnowAFramework
{
static class Program
{
static void Main(string[] args)
{
#region About me
// Jeff Moser
// jeff@moserware.com
// My Blog: http://www.moserware.com/
#endregion
// I gave this talk at Indy Code Camp on Saturday, April 26, 2008
// The format was just stepping through this file by hitting F11
// in the debugger and discussing things as they came up.
#region
// Poll
// How many of you have used:
// 1. System.Text.StringBuilder
// 2. System.IO.Path
// 3. System.Text.RegularExpressions.Regex
// 4. System.IFormattable
// 5. System.AppDomain
// 6. System.Linq.Expressions.Expression
// Poll results: mostly 1 & 2, some 3, couple of 5.
#endregion
#region Startup Things (a.k.a. "How did we get here?")
// Your OS starts a process:
// System.Diagnostics.Process.Start()
// Then a thread gets queued up:
// System.Threading.Thread.Start()
// Now, the CLR will execute your assembly:
// System.AppDomain.ExecuteAssembly()
// System.AppDomain._nExecuteAssembly (internal call)
// It does this by loading your code and finding the
// static method marked with ".entrypoint" in IL
System.Diagnostics.Process myProcess = Process.GetCurrentProcess();
AppDomain myAppDomain = AppDomain.CurrentDomain;
// I'll be using C# 3's type-inference from now on by
// using "var". It's just as if I typed everything out:
var myAssembly = Assembly.GetEntryAssembly();
var myThread = Thread.CurrentThread;
var greeting = "Hello World!";
#endregion
#region Beginner
WarmupClasses();
ExploringSystemCollectionsGeneric();
FunWithStrings();
BoolVsEnumDebate();
#endregion
#region Intermediate
ILoveRegularExpressions();
OverviewOfStreams();
DabblingWithSecurity();
HowDoWeRelate();
PeeringIntoIDisposable();
AggregatesMakeMortHappy();
// GAC -- look in the virtual C:\windows\assembly folder
#endregion
#region A bit more challenging
UnderstandingHowLinqIsImplemented();
DoesYourCodePassTheTurkeyTest();
SewingWithThreads();
EventsCanBeTricky();
BriefCoverageOfTracing();
InterestingInternalClasses();
// Juval's C# Coding Conventions
// http://www.idesign.net/idesign/download/IDesign%20CSharp%20Coding%20Standard.zip
// Recommend reading "Framework Design Guidelines" by Cwalina and Abrams
// digest at: http://blogs.msdn.com/kcwalina/archive/2008/04/09/FDGDigest.aspx
// (new updated edition coming out later this year)
// naming: callback, canceled, Email, FileName, HashTable, Id id, Indexes, Ok, ok, Pi, signIn, userName, whitespace, writable vs writeable
// Expert .NET 2.0 IL Assembler by Serge Lidin
// Obscure classes, but helpful
// System.WeakReference
// System.ComponentModel.ISynchronizeInvoke
#endregion
// Questions?
}
private static void WarmupClasses()
{
#region System.IO.Path
// Very useful class I didn't know about for awhile
// Helps manipulate file paths
var sampleFileName = @"C:\Windows\System32\calc.exe";
var sampleDirectory = Path.GetDirectoryName(sampleFileName);
var isPathRootedYes = Path.IsPathRooted(sampleFileName);
var isPathRootedNo = Path.IsPathRooted("helloworld.exe");
var calcexe = Path.GetFileName(sampleFileName);
var otherFileInDirectory = Path.Combine(sampleDirectory, "cmd.exe");
#endregion
#region System.Math (Show #293)
// Convenient math functions
var max = Math.Max(4, 2);
var min = Math.Min(4, 2);
var abs = Math.Abs(-42);
var tan = Math.Tan(Math.PI / 4);
var floor = Math.Floor(4.2);
var sin = Math.Sin(Math.PI / 4);
// This one returns the full 64 bit result
var bigMul = Math.BigMul(int.MaxValue, int.MaxValue);
#endregion
#region System.Console (Show #268)
var capsLockOn = Console.CapsLock;
var numberLockOn = Console.NumberLock;
Console.Title = "Better Know a Framework";
Console.BackgroundColor = ConsoleColor.Blue;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Clear();
Console.WriteLine("Hello World!");
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Gray;
Console.Clear();
#endregion
#region Convert (Show #280)
var trueIs1point0 = Convert.ToDouble(true);
var goingBeyondInt32 = Convert.ToUInt32(int.MaxValue) + 1;
var base64Example = Convert.ToBase64String(BitConverter.GetBytes(0xDEADBEEF));
var returnTrip = Convert.FromBase64String(base64Example);
#endregion
#region System.Xml.XmlConvert
var xmlDate = XmlConvert.ToString(DateTime.Now);
var traditionalDate = DateTime.Now.ToString();
var xmlTrue = XmlConvert.ToBoolean("1")
&&
XmlConvert.ToBoolean("true");
#endregion
#region System.Environment (Show #269)
var userName = Environment.UserName;
// The following method is very useful because these "special folders" can
// be in many different spots depending on the OS and language
var programFilesFolder = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
#endregion
#region System.TimeSpan (Show #294)
DateTime thirtyMinutesFromNow = DateTime.Now + TimeSpan.FromMinutes(30);
#endregion
}
private static void FunWithStrings()
{
#region System.Text.StringBuilder (Show #249)
// If you are concatenating several strings together, this is bad:
var hello = "Hello";
var space = " ";
var world = "World!";
var badHelloWorld = hello + space + world; // (hello + space) + world
// The reason is that everytime you concat two things, a temporary
// intermediate object is created.
// Here's the preferred way:
var sb = new System.Text.StringBuilder();
sb.Append("Hello");
sb.Append(" ");
sb.Append("World!");
var helloWorld = sb.ToString();
// Someone in the audience asked when you should start caring about
// the inefficiencies. I said if you know you're going to be concatenating
// more than 5 strings than prefer StringBuilder
#endregion
#region String formatting and IFormattable (Show #315)
// note that MyNumber is my custom class that implments IFormattable
var currentYear = new MyNumber(DateTime.Now.Year);
// Here we're showing off the custom formatting.
// Note that "roman" is my own format.
string formattedGreeting
= string.Format(@"Hello! My name is {0} and today is {1:D}." +
"The year is {2:0,0} ({2:roman})",
"Jeff", DateTime.Now, currentYear);
// Here is the type of thing string.Format is calling under the covers
string year1999 = (new MyNumber(1999)).ToString("roman", CultureInfo.CurrentCulture);
string directoryService = string.Format("{0:(###) ###-####}", 8005551212);
// More string formatting examples at
// http://blog.stevex.net/index.php/string-formatting-in-csharp/
#endregion
}
private static void BoolVsEnumDebate()
{
// Which is easier to understand?
var exhibitA = string.Compare("Jeff", "jeff", true) == 0;
var exhibitB = string.Compare("Jeff", "jeff", StringComparison.CurrentCultureIgnoreCase) == 0;
// If you're designing public APIs, tend to prefer enums (e.g. StringComparison) over booleans.
// It just makes things easier to read.
}
private static void ILoveRegularExpressions()
{
// Here we're only going to show a few numbers. But just imagine
// If you had to process thousands or even millions.
string phoneNumbersOldFormat = "(800) 555-1212, (317) 222-1234, 765-4951234";
var newFormat = Regex.Replace(phoneNumbersOldFormat, @"[^\d,]*
(?<areaCode>\d{3})
\D*
(?<first3>\d{3})
\D*
(?<last4>\d{4})
[^,]*",
"${areaCode}.${first3}.${last4} ", RegexOptions.IgnorePatternWhitespace);
var isSsn = Regex.IsMatch("123-45-6789", @"\d{3}-\d{2}-\d{4}");
// many more possibilities
// see http://msdn2.microsoft.com/en-us/library/hs600312.aspx
}
private static void OverviewOfStreams()
{
// Streams are a generic concept in .net
// They're very swappable and connectable sort of like Legos.
// Let's create a stream that writes text, then compresses it,
// then encrypts it, then stores it in memory.
var memory = new MemoryStream();
var secretKey = "My Secret Password!";
var salt = new byte[32];
var iv = new byte[16];
var passwordBytes = new Rfc2898DeriveBytes(secretKey, salt);
var encryptionAlgorithm = Aes.Create().CreateEncryptor(passwordBytes.GetBytes(32), iv);
var encryption = new CryptoStream(memory, encryptionAlgorithm, CryptoStreamMode.Write);
var compression = new GZipStream(encryption, CompressionMode.Compress);
var writer = new StreamWriter(compression);
// The pipeline/stream is
// writer => compression => encryption => memory
// Question: Why would this be a bad idea?
// writer => encryption => compression => memory
// As I mentioned in the talk and people in the audience got,
// you wouldn't want to do the latter because encrypted data
// should be random and won't compress much.
// Quick way to generate "Hello" repeated 500 times.
var input = string.Join(" ", Enumerable.Repeat("Hello", 500).ToArray());
var inputSize = input.Length;
writer.Write(input);
writer.Flush();
var outputBytes = memory.ToArray();
// Write the file from the MemoryStream to disk:
var temporaryFileName = Path.GetTempFileName();
using (var outputStream = File.OpenWrite(temporaryFileName))
{
memory.WriteTo(outputStream);
}
memory.Close();
// No time to cover IsolateStorageStream, but it's useful.
}
private static void AggregatesMakeMortHappy()
{
// Aggregate Pattern
// See "Framework Design Guidelines" pages 240-243
// Mort is someone who wants to get things done.
// See: http://www.codinghorror.com/blog/archives/001004.html
// Mort likes 1 - 2 lines of code to get things done:
// var fileContents = File.ReadAllBytes(path)
// var wc = new System.Net.WebClient();
// var googlePage = wc.DownloadString("http://www.google.com/");
}
private static void DabblingWithSecurity()
{
#region System.Security.Cryptography.RandomNumberGenerator (Show #244)
// Question: Why not use System.Random for generating keys?
// Answer: It can be a source of easy attack:
// http://en.wikipedia.org/wiki/Random_number_generator_attack
var rng = RandomNumberGenerator.Create();
var myRandomBytes = new byte[10];
rng.GetBytes(myRandomBytes);
rng.GetBytes(myRandomBytes);
#endregion
#region System.Security.Cryptography.ProtectedData
// Here's a quick way of storing secrets in a way that only the current
// user can decrypt:
var pd = ProtectedData.Protect(ASCIIEncoding.ASCII.GetBytes("Hello World"), null, DataProtectionScope.CurrentUser);
var pdString = Convert.ToBase64String(pd);
var unprotected = ASCIIEncoding.ASCII.GetString(ProtectedData.Unprotect(pd, null, DataProtectionScope.CurrentUser));
#endregion
}
private static void InterestingInternalClasses()
{
// Didn't have time to cover this, but check out:
// see http://www.moserware.com/2008/01/borrowing-ideas-from-3-interesting.html
// System.Linq.Strings
// System.Linq.Error
// Microsoft.Contract.Contracts
}
private static void HowDoWeRelate()
{
#region System.IComparable (Show #305)
// You can completely change how your class is sorted/compared.
// Note that "MyNumber" class implements IComparable and its
// behavior is to sort in descending order.
var list = new List<MyNumber>();
list.Add(new MyNumber(5));
list.Add(new MyNumber(1));
list.Add(new MyNumber(3));
list.Add(new MyNumber(2));
list.Sort();
#endregion
// I didn't cover this but:
// If just want equality, override object.Equals(obj)
// Question: Why might you want to implement IEquatable?
// Answer: It helps value types not box
// By the way, if you overload Equals, you should overload GetHashCode()
// or else things that depend on hash codes (like Dictionary) will
// cause confusing results.
}
private static void DoesYourCodePassTheTurkeyTest()
{
// I gave a brief overview of this post:
// http://www.moserware.com/2008/02/does-your-code-pass-turkey-test.html
Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
DateTime julyFourthA = DateTime.Parse("07/04/2008");
DateTime julyFourthB = DateTime.Parse("07/04/2008", DateTimeFormatInfo.InvariantInfo);
double fourPointFiveA = double.Parse("4.5");
double fourPointFiveB = double.Parse("4.5", NumberFormatInfo.InvariantInfo);
string fileUpperA = "file".ToUpper();
string fileUpperB = "file".ToUpperInvariant();
bool wacky = string.Equals(fileUpperA, fileUpperB);
bool sampleCompare = "File".Equals(fileUpperB, StringComparison.OrdinalIgnoreCase);
}
private static void SewingWithThreads()
{
#region Threading
// Simple class to do things in the background
var bgw = new System.ComponentModel.BackgroundWorker();
// Threads can be expensive to create. So it's better to have a "pool"
// of threads that you can use and then have go to sleep:
ThreadPool.QueueUserWorkItem(
new WaitCallback(
delegate(object args)
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
}
}));
var domainNameToIP = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
object sync = new object();
// Imagine that you had a naive DNS server that locked every time
// it touched the dictionary (including reads)
lock (sync)
{
domainNameToIP["Moserware.com"] = "72.3.2.1";
}
// Question: Anything bad about this?
// Answer: YES! Very inefficient. Don't need to lock out
// people unless a writer occurs. It's better to use
// a ReaderWriterLock (or ReaderWriterLockSlim) instead.
var rwl = new ReaderWriterLock();
try
{
rwl.AcquireWriterLock(Timeout.Infinite);
domainNameToIP["moserware.com"] = "72.14.207.121";
}
finally
{
rwl.ReleaseWriterLock();
}
// no time to cover semaphore, autoresetevent, manualresetevent, waithandle, waitall
//int myNum = 1;
//Interlocked.Increment(ref myNum);
// Future:
// Parallel.For(0, 1000000, i=>
// {
// result[i] = ReallyExpensiveFunction(i);
// }
// automatically scales to multiple cores.
// See http://msdn2.microsoft.com/en-us/magazine/cc163340.aspx
#endregion
}
private static void EventsCanBeTricky()
{
var c = new SampleEventRaisingClass();
c.SomethingHappened += (s, e) => Console.WriteLine("Something Happened");
c.OnSomethingHappened();
}
private static void BriefCoverageOfTracing()
{
#region Shows #333 and #334
// printf all grown up
// System.Diagnostics
Trace.Listeners.Add(new TextWriterTraceListener(Path.GetTempFileName()));
Trace.WriteLine("Hello from BriefCoverageOfTracing");
// Can be used for diagnosing bugs on customers machines if you have Trace logs.
#endregion
}
private static void UnderstandingHowLinqIsImplemented()
{
// A look under the covers of how LINQ is implemented:
var numbers1To50 = Enumerable.Range(1, 50);
// This LINQ statement in the SQL-esque syntax:
var evenNumbersSquared = from n in numbers1To50 where (n % 2 == 0) select n * n;
// is identical to this:
var alternately = numbers1To50.Where(n => n % 2 == 0).Select(n => n * n);
// but could also be rewritten this way (note that I define GetEvens and GetSquares below)
var orHowAbout = numbers1To50.GetEvens().GetSquares();
// You can do this manually too:
Func<int, bool> isEven = n => (n % 2) == 0;
// or using the uglier C# 2 syntax:
Func<int, int> squarer = delegate(int n)
{
return n * n;
};
var yetAnother = numbers1To50.Where(isEven).Select(squarer);
// More advanced uses of LINQ methods:
Func<int, int> factorial = n => Enumerable.Range(1, n).Aggregate(1, (a, i) => a * i);
var fact5 = factorial(5);
var sum1To10 = Enumerable.Range(1, 10).Sum();
// Express yourself..
// See: http://www.codeproject.com/KB/cs/explore_lamda_exp.aspx
Expression<Func<int, int>> square = x => x * x;
BinaryExpression squareplus2 = Expression.Add(square.Body,
Expression.Constant(2));
Expression<Func<int, int>> expr = Expression.Lambda<Func<int, int>>(squareplus2,
square.Parameters);
Func<int, int> compile = expr.Compile();
var result = compile(5);
// LINQ works with both Func's and Expression's
// Expressions are used when you want to understand more of the intent
// of what the programmer wants to do (typically multiple operations)
// Show #319 - IQueryable and IQueryProvider
// The best way to learn for me was to listen straight from Anders:
// http://langnetsymposium.com/talks.asp (click on Anders' talk)
}
private static IEnumerable<int> GetEvens(this IEnumerable<int> collection)
{
foreach (int n in collection)
{
if (n % 2 == 0)
{
yield return n;
}
}
}
private static IEnumerable<int> GetSquares(this IEnumerable<int> collection)
{
foreach (int n in collection)
{
yield return n * n;
}
}
private static void PeeringIntoIDisposable()
{
#region IDisposable (Show #287)
FileStream fs = null;
string path = @"C:\windows\system32\calc.exe";
var firstKB = new byte[1024];
// This type of code is so repetitive...
try
{
fs = new FileStream(path, FileMode.Open, FileAccess.Read);
fs.Read(firstKB, 0, firstKB.Length);
}
finally
{
fs.Close();
}
// ..that this syntax does the similar thing since FileStream
// implements IDisposable
using (fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
fs.Read(firstKB, 0, firstKB.Length);
// fs.Dispose() called automatically which calls fs.Close()
}
// when your object is disposed, you should call GC.SuppressFinalize
// FYI: foreach loops use dispose pattern on the enumerator
// See http://www.moserware.com/2008/02/for-loops-using-i-i-enumerators-or-none.html
#endregion
}
private static void ExploringSystemCollectionsGeneric()
{
// Arrays are used internally for collections.
// You can usually forget this, but sometimes it's good to know
#region System.Collections.Generic
#region System.Collections.Generic.List<T>
var list = new List<int>(Enumerable.Range(1, 3));
list.Add(4);
var capacity = list.Capacity;
// Question: How fast will the following statement run?
list.Add(5);
// Answer: Slower than you might think since a new array is created of size
// 8 and elements are copied from the old one to the new one.
capacity = list.Capacity;
// Now you see why this is bad:
for (int i = 0; i < 10; i++)
{
list.Insert(0, i);
}
// Every time you insert at position 0, you have to create a new array and
// copy the old contents.
list.AddRange(Enumerable.Range(6, 10));
// This command reduces the size of the array to just what you need:
list.TrimExcess();
capacity = list.Capacity;
list.ForEach(i => Console.WriteLine(i));
// Same thing using C# 2 syntax
list.ForEach(delegate(int i) { Console.WriteLine(i); });
var trueForAll = list.TrueForAll(i => i > 0);
list.Sort();
#endregion
#region System.Collections.Generic.Dictionary<T>
// This type of constructor isn't as well known:
var dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
// But it lets you have case insensitive key operations:
dictionary["one"] = 1;
dictionary["Two"] = 2;
dictionary["ThReE"] = 3;
// so you can do this:
var mySum = dictionary["one"] + dictionary["two"] + dictionary["three"];
// If you do the normal way:
dictionary = new Dictionary<string, int>();
dictionary["one"] = 1;
dictionary["Two"] = 2;
dictionary["ThReE"] = 3;
// You get errors:
try
{
mySum = dictionary["one"] + dictionary["two"] + dictionary["three"];
}
catch (KeyNotFoundException knfe)
{
knfe = knfe;
}
#endregion
#region Show 253
#region System.Collections.Generic.Stack<T>
var stack = new Stack<int>(Enumerable.Range(1, 10));
var stackVal = stack.Peek();
stackVal = stack.Pop();
stack.Push(42);
stackVal = stack.Pop();
#endregion
#region System.Collections.Generic.Queue<T>
var queue = new Queue<int>(Enumerable.Range(1, 10));
var queueValue = queue.Dequeue();
queue.Enqueue(11);
#endregion
#endregion
#region System.Collections.Generic.HashSet<T>
var hs1To10 = new HashSet<int>(Enumerable.Range(1, 10));
// note the argument type
hs1To10.IntersectWith(Enumerable.Range(5, 11));
hs1To10 = new HashSet<int>(Enumerable.Range(1, 10));
hs1To10.UnionWith(Enumerable.Range(5, 11));
hs1To10 = new HashSet<int>(Enumerable.Range(1, 10));
hs1To10.SymmetricExceptWith(Enumerable.Range(5, 11));
// Note that LINQ to objects has set operations as well.
// Many things you do in code can be thought of as operations on sets.
#endregion
// Did not cover, but good to know
// ReadOnlyCollection -- good return type
#endregion
}
#region AppDomain Sandbox
// Didn't have time to cover this, but AppDomains provide interesting isolation.
// This is how SilverLight can run arbitrary code safely.
// From http://msdn2.microsoft.com/en-us/library/bb763046.aspx
private static void ShowAppDomainSandbox()
{
// Create the permission set to grant to other assemblies.
// In this case we are granting the permissions found in the LocalIntranet zone.
PermissionSet pset = GetNamedPermissionSet("LocalIntranet");
if (pset == null)
return;
// Optionally you can create your own permission set by explicitly adding permissions.
// PermissionSet pset = new PermissionSet(PermissionState.None);
// pset.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
// pset.AddPermission(new UIPermission(PermissionState.Unrestricted));
AppDomainSetup ads = new AppDomainSetup();
// Identify the folder to use for the sandbox.
ads.ApplicationBase = "C:\\Sandbox";
// Copy the application to be executed to the sandbox.
File.Copy("HelloWorld.exe", "C:\\sandbox\\HelloWorld.exe", true);
Evidence hostEvidence = new Evidence();
// Commenting out the following two statements has no effect on the sample.
// The grant set is determined by the grantSet parameter, not the evidence
// for the assembly. However, the evidence can be used for other reasons,
// for example, isolated storage.
hostEvidence.AddHost(new Zone(SecurityZone.Intranet));
hostEvidence.AddHost(new Url("C:\\Sandbox"));
// Create the sandboxed domain.
AppDomain sandbox = AppDomain.CreateDomain(
"Sandboxed Domain",
hostEvidence,
ads,
pset,
GetStrongName(Assembly.GetExecutingAssembly()));
sandbox.ExecuteAssemblyByName("HelloWorld");
}
/// <summary>
/// Get a strong name that matches the specified assembly.
/// </summary>
/// <exception cref="ArgumentNullException">
/// if <paramref name="assembly"/> is null
/// </exception>
/// <exception cref="InvalidOperationException">
/// if <paramref name="assembly"/> does not represent a strongly named assembly
/// </exception>
/// <param name="assembly">Assembly to create a StrongName for</param>
/// <returns>A StrongName for the given assembly</returns>
///
public static StrongName GetStrongName(Assembly assembly)
{
if (assembly == null)
throw new ArgumentNullException("assembly");
AssemblyName assemblyName = assembly.GetName();
Debug.Assert(assemblyName != null, "Could not get assembly name");
// Get the public key blob.
byte[] publicKey = assemblyName.GetPublicKey();
if (publicKey == null || publicKey.Length == 0)
throw new InvalidOperationException("Assembly is not strongly named");
StrongNamePublicKeyBlob keyBlob = new StrongNamePublicKeyBlob(publicKey);
// Return the strong name.
return new StrongName(keyBlob, assemblyName.Name, assemblyName.Version);
}
private static PermissionSet GetNamedPermissionSet(string name)
{
IEnumerator policyEnumerator = SecurityManager.PolicyHierarchy();
// Move through the policy levels to the machine policy level.
while (policyEnumerator.MoveNext())
{
PolicyLevel currentLevel = (PolicyLevel)policyEnumerator.Current;
if (currentLevel.Label == "Machine")
{
NamedPermissionSet copy = currentLevel.GetNamedPermissionSet(name);
return (PermissionSet)copy;
}
}
return null;
}
#endregion
}
internal class MyNumber : IFormattable, IComparable
{
private readonly int m_Number;
public MyNumber(int number)
{
m_Number = number;
}
#region IFormattable Members
public string ToString(string format, IFormatProvider formatProvider)
{
if (format.Equals("roman", StringComparison.OrdinalIgnoreCase))
{
return RomanNumeral.Encode(m_Number);
}
else
{
return m_Number.ToString(format, formatProvider);
}
}
#endregion
#region IComparable Members
public int CompareTo(object obj)
{
return -1 * m_Number.CompareTo((obj as MyNumber).m_Number);
}
#endregion
}
class RomanNumeral
{
static private Dictionary<char, int> m_LetterToValue = new Dictionary<char, int>();
static private char[] m_MostToLeast = new char[] { 'M', 'D', 'C', 'L', 'X', 'V', 'I' };
static RomanNumeral()
{
m_LetterToValue['I'] = 1;
m_LetterToValue['V'] = 5;
m_LetterToValue['X'] = 10;
m_LetterToValue['L'] = 50;
m_LetterToValue['C'] = 100;
m_LetterToValue['D'] = 500;
m_LetterToValue['M'] = 1000;
}
public static string Encode(int number)
{
var sb = new StringBuilder();
int remainder = number;
while (remainder > 0)
{
foreach (char c in m_MostToLeast)
{
if ((remainder - m_LetterToValue[c]) >= 0)
{
sb.Append(c);
remainder -= m_LetterToValue[c];
break;
}
}
}
// Now to optimizations
sb.Replace("VIIII", "IX");
sb.Replace("IIII", "IV");
sb.Replace("XXXX", "XL");
sb.Replace("LXXXX", "XC");
sb.Replace("CCCC", "CD");
sb.Replace("DCD", "CM");
sb.Replace("LXL", "XC");
sb.Replace("DCCCC", "CM");
return sb.ToString();
}
}
class SampleEventRaisingClass
{
public event EventHandler<EventArgs> SomethingHappened;
public void OnSomethingHappened()
{
// how many things can you count wrong in this one line of code?
SomethingHappened(this, EventArgs.Empty);
// HINT:
// 1. It shouldn't be public
// 2. should check for null:
// if (SomethingHappened != null)
// {
// SomethingHappened(this, EventArgs.Empty);
// }
// 3. But even that is wrong, there exists a possibility that
// SomethingHappened could become null after you checked it:
// var handler = SomethingHappened;
// if (handler != null)
// {
// handler(this, EventArgs.Empty);
// }
// What happens if your handlers throw an exception? What happens
// if your handler should be called on the UI thread, etc...
// Lots of things to think about, that's why you should really look
// at Juval's EventsHelper class:
// https://opensvn.csie.org/traccgi/BuckRogers/browser/trunk/Networking/EventsHelper.cs
// and use that everywhere instead of manually doing all these checks
// each time.
}
}
}
