<log4net>
<appender name="RollingFileAppenderBackend" type="log4net.Appender.RollingFileAppender">
<file value="C:\inetpub\logs\Backend.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1000KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%t] %-5p: %m%n" />
</layout>
</appender>
<appender name="RollingFileAppenderWebUI" type="log4net.Appender.RollingFileAppender">
<file value="C:\inetpub\logs\WebUI.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1000KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%t] %-5p: %m%n" />
</layout>
</appender>
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%d [%t] %-5p: %m%n" />
</layout>
</appender>
<logger name="Backendlogger">
<level value="DEBUG" />
<appender-ref ref="RollingFileAppenderBackend" />
<appender-ref ref="EventLogAppender" />
<appender-ref ref="ConsoleAppender" />
</logger>
<logger name="UILogger">
<level value="DEBUG" />
<appender-ref ref="RollingFileAppenderWebUI" />
<appender-ref ref="ConsoleAppender" />
</logger>
</log4net>
Saturday, January 12, 2008
Configure Log4Net To Log Different Files For Different Projects
Log4Net is a free logging helper running in .NET environment (ported from Log4j). By configuration setting you can log message to files, event log, console window, etc. The common usage is logging to one file, but you can also separate the log files for different projects if you want. Following configuration is an example to separate web UI project logs and backend class library logs:
Tag:
Other
Thursday, October 25, 2007
Javascript Random Text
Following javascript is to create some mock contacts for UI testing purpose:
function createMockContacts(contactNumber) {
var contacts = new Array();
for (var i = 0; i < contactNumber; i++) {
var contact = new Object();
contact.Name = randomText(8);
contact.Company = randomText(16);
contact.Phone = randomText(9, "phone");
contact.Email = randomText(9, "email");
contact.Address = randomText(20);
contacts.push(contact);
}
return contacts;
}
function randomText(length, type) {
if ( type == "phone")
var chars = "1234567890";
else
var chars = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var text = "";
for (var i = 0; i < length; i++) {
text += chars.charAt(Math.floor(Math.random() * chars.length));
}
if (type == "email" && text.length > 3)
text = text.slice(0, 3) + "@" + text.slice(3) + ".com";
return text;
}
Return is an array of javascript objects:
contacts[0].Name = "7k15zlQQ" contacts[0].Company = "sTOX2EVFhsBXJFIn" contacts[0].Phone = "881414055" contacts[0].Email = "3Ic@Adu2N3.com" contacts[0].Address = "lN3kSwJCJC3m7gC8zdZy" contacts[1].Name = "IZYkS3gI" ...
Tag:
Script
Thursday, October 11, 2007
.NET Repository Pattern
Domain Driven Design (DDD) attracts quite a lot of attentions in recent years. Repository pattern is one important pattern in DDD. It mediates between the domain and data mapping layers, or a simple delegation of responsibility away from the domain objects.
Consider reading GeoCode from database, we may have following code:
http://martinfowler.com/eaaCatalog/repository.html
http://martinfowler.com/articles/injection.html
Consider reading GeoCode from database, we may have following code:
public class GeoCodeSuch code works but it's not flexible and it has direct dependency to the backend store. With repository pattern we need to declare an interface and have a separate implementation:
{
public double Latitude;
public double Longitude;
public GeoCode(double lat, double lon)
{
Latitude = lat;
Longitude = lon;
}
public GeoCode GetByPostalCode(string postalCode)
{
GeoCode gc = null;
string connectionString = "....";
string sqlQuery = "SELECT Latitude, Longitude FROM GeoCode WHERE PostalCode = @PostalCode";
try
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand DBCommand = new SqlCommand(sqlQuery, connection);
DBCommand.Parameters.AddWithValue("@PostalCode", postalCode);
using (SqlDataReader reader = DBCommand.ExecuteReader())
{
if (reader.HasRows)
{
gc = new GeoCode(Convert.ToDouble(reader["Latitude"]), Convert.ToDouble(reader["Longitude"]));
}
}
}
}
catch (Exception ex)
{
Logger.LogException(ex);
}
return gc;
}
}
public class GeoCodeA wrapper class in service layer exposes all related functions:
{
public double Latitude;
public double Longitude;
public GeoCode(double lat, double lon)
{
Latitude = lat;
Longitude = lon;
}
}
public interface IGeoCodeRepository
{
GeoCode GetByPostalCode(string postalCode);
}
public class SQLServerGeoCodeRepository : IGeoCodeRepository
{
private string ConnectionString;
public SQLServerGeoCodeRepository()
{
ConnectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
}
public SQLServerGeoCodeRepository(string connectionString)
{
ConnectionString = connectionString;
}
public GeoCode GetByPostalCode(string postalCode)
{
GeoCode gc = null;
string sqlQuery = "SELECT Latitude, Longitude FROM GeoCode WHERE PostalCode = @PostalCode";
try
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
SqlCommand DBCommand = new SqlCommand(sqlQuery, connection);
DBCommand.Parameters.AddWithValue("@PostalCode", postalCode);
using (SqlDataReader reader = DBCommand.ExecuteReader())
{
if (reader.HasRows)
{
gc = new GeoCode(Convert.ToDouble(reader["Latitude"]), Convert.ToDouble(reader["Longitude"]));
}
}
}
}
catch (Exception ex)
{
Logger.LogException(ex);
}
return gc;
}
}
public class GeoCodeServiceWith repository pattern we can easily extend current implementation. For example, if we need to use web service to get the geo code, we can simply add a new implementation without changing any existing code:
{
private IGeoCodeRepository _repository;
public GeoCodeService(IGeoCodeRepository repository)
{
_repository = repository;
}
public GeoCode GetByPostalCode(string postalCode)
{
return _repository.GetByPostalCode(postalCode);
}
}
class Program
{
static void Main(string[] args)
{
IGeoCodeRepository repository = new SQLServerGeoCodeRepository();
GeoCodeService service = new GeoCodeService(repository);
GeoCode gc = service.GetByPostalCode("A1A1A1");
}
}
public class WebServiceGeoCodeRepository : IGeoCodeRepositoryThe other advantage of using repository pattern is the testability. For example we can create a fake data repository:
{
private string WebServiceUrl;
public WebServiceGeoCodeRepository()
{
WebServiceUrl = ConfigurationManager.AppSettings["WebServiceUrl"];
}
public WebServiceGeoCodeRepository(string webServiceUrl)
{
WebServiceUrl = webServiceUrl;
}
public GeoCode GetByPostalCode(string postalCode)
{
GeoCode gc = null;
//Get GeoCode by web service
return gc;
}
}
public class Program
{
static void Main(string[] args)
{
IGeoCodeRepository repository = new WebServiceGeoCodeRepository();
GeoCodeService service = new GeoCodeService(repository);
GeoCode gc = service.GetByPostalCode("A1A1A1");
}
}
public class FakeGeoCodeRepository : IGeoCodeRepositoryThe unit test becomes simple:
{
public GeoCode GetByPostalCode(string postalCode)
{
GeoCode gc = null;
double fakeCode = 111.111;
if (postalCode.StartsWith("A", StringComparison.InvariantCultureIgnoreCase))
fakeCode = 123.123;
gc = new GeoCode(fakeCode, fakeCode);
return gc;
}
}
[TestMethod()]Related topics:
public void GetByPostalCodeTest()
{
IGeoCodeRepository repository = new FakeGeoCodeRepository();
GeoCodeService target = new GeoCodeService(repository);
string postalCode = "M1M1M1";
double expected = 111.111;
GeoCode actual;
actual = target.GetByPostalCode(postalCode);
Assert.AreEqual(expected, actual.Latitude);
Assert.AreEqual(expected, actual.Longitude);
postalCode = "A1A1A1";
expected = 123.123;
actual = target.GetByPostalCode(postalCode);
Assert.AreEqual(expected, actual.Latitude);
Assert.AreEqual(expected, actual.Longitude);
}
http://martinfowler.com/eaaCatalog/repository.html
http://martinfowler.com/articles/injection.html
Wednesday, September 26, 2007
ASP.NET Impersonate Application Pool Account To Access Database
In an ASP.NET web application, we use a fixed account to connect to SQL server. Although we could encrypt the user name and password in the connection string, admin still suggested to use Windows Authentication so that there's no any user credential information stored in the file system.
One simple way to achieve that is create an application pool for the web app with a service account, and grant the permission for that service account inside the SQL server, then wrap all ADO.NET code inside a System.Web.Hosting.HostingEnvironment.Impersonate() block:
One simple way to achieve that is create an application pool for the web app with a service account, and grant the permission for that service account inside the SQL server, then wrap all ADO.NET code inside a System.Web.Hosting.HostingEnvironment.Impersonate() block:
using (System.Web.Hosting.HostingEnvironment.Impersonate())
{
using (SqlConnection connection = new SqlConnection(connString))
{
SqlCommand cmd = new SqlCommand(spStoredProcedureName, connection);
//... other ADO.NET code
}
}
Thursday, August 02, 2007
Array.ConvertAll and List.ConvertAll
Just noticed this handy method:
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
int[] numbers = new int[] { 65, 66, 67 };
char[] chars = Array.ConvertAll(numbers, delegate(int number)
{ return (char)number; });
//Print "ABC"
Array.ForEach(chars, delegate(char ch) { Console.Write(ch); });
Console.WriteLine();
List<int> numberList = new List<int>(numbers);
List<char> charList = numberList.ConvertAll(delegate(int number)
{ return (char)number; });
//Print "ABC"
charList.ForEach(delegate(char ch) { Console.Write(ch); });
Console.Read();
}
}
Tag:
.NET
Saturday, July 07, 2007
Singleton Pattern - A Logger Example
The idea of a singleton is to have a class that has only one instance and a global point of access to the instance. Following code is a simple singleton C# implementation:
Singleton pattern is very useful in some use cases. Here's a real life example. I needed a file logger for a simple web service application used in a small company. Enterprise library, logging application block, and even log4Net are just too heavy for such small application. In addition, the client is rejective to third-party tools.
I decided to write a file logger by myself. A stream writer was used, but I don't want multiple instances of stream writers being created and used at the same time. The IO is expensive and multiple accesses to a single file is not safe. Singleton came to play and only one instance of logger and one stream writer is created:
public class SingletonThe above implementation creates a new static instance during the class initialization. The singleton instance is retrieved by a static GetInstance method. An alternative is expose the singleton object by read-only property without a setter. The code needs a little bit change if a lazy creation is required:
{
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton GetInstance()
{
return instance;
}
}
public class SingletonSyncLock in above code is for thread-safe consideration. In addition, the singleton class could be sealed if you don't like people to inherit from it and break the singleton principle.
{
private static volatile Singleton instance;
private static object syncObj = new Object();
private Singleton() {}
public static Singleton GetInstance()
{
if (instance == null)
{
lock (syncObj)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
Singleton pattern is very useful in some use cases. Here's a real life example. I needed a file logger for a simple web service application used in a small company. Enterprise library, logging application block, and even log4Net are just too heavy for such small application. In addition, the client is rejective to third-party tools.
I decided to write a file logger by myself. A stream writer was used, but I don't want multiple instances of stream writers being created and used at the same time. The IO is expensive and multiple accesses to a single file is not safe. Singleton came to play and only one instance of logger and one stream writer is created:
using System;
using System.IO;
using System.Configuration;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
Logger.WriteLog("Jus a test.");
Logger.WriteLog(LogSeverity.Info, "Another test.");
Console.Read();
}
}
// A helper static class to get access the logger
public static class Logger
{
private static readonly FileLogger _logger = FileLogger.GetInstance();
public static void WriteLog(String log)
{
_logger.Write(log);
}
public static void WriteLog(LogSeverity logLevel, String log)
{
_logger.Write(logLevel, log);
}
}
// Enumeration for log severity
public enum LogSeverity { Error, Warning, Info, Debug };
// FileLogger class with Singleton pattern
internal sealed class FileLogger : IDisposable
{
private static FileLogger _instance = null; // Singleton object
private static object _syncLock = new object(); // Sync lock
internal StreamWriter _writer; // Stream writer writting to the log file
internal string _path = "C:\\Logs"; // Default folder
internal string _absolutePath; // C:\Logs\MachineName.log
internal string _fileName; // MachineName.log
internal int _logFileSize = 2; // New log file when its size bigger than 2 MB
internal LogSeverity _defaultSeverity = LogSeverity.Error; // Default severity
// Private constructor
private FileLogger()
{
GetConfigSettings();
_fileName = string.IsNullOrEmpty(Environment.MachineName) ?
"LogFile.log" : Environment.MachineName + ".log";
_absolutePath = _path + "\\" + _fileName;
if (!Directory.Exists(_path))
{
Directory.CreateDirectory(_path);
}
_writer = new StreamWriter(_absolutePath, true);
}
// Public static method to get Singleton object
public static FileLogger GetInstance()
{
if (_instance == null)
{
lock (_syncLock)
{
if (_instance == null)
{
_instance = new FileLogger();
}
}
}
return _instance;
}
// Check if the log file exceeds the maximum size
private void CheckFileSize()
{
if (_writer.BaseStream.Length >= (_logFileSize * 1024 * 1024))
{
this.Dispose();
string backupFile = string.Format("{0}\\{1}-{2}.log",
_path, Environment.MachineName, DateTime.Now.ToString("yyyyMMdd-HHmmsss"));
try
{
while (File.Exists(backupFile))
{
backupFile = backupFile.Replace(".log", "_1.log");
}
File.Move(_absolutePath, backupFile);
this.Dispose();
_writer = new StreamWriter(_absolutePath, true);
}
catch (Exception ex)
{
WriteToEventLog(ex.Message);
}
}
}
// Get default setting from configuraiton file
private void GetConfigSettings()
{
string appSetting = ConfigurationManager.AppSettings["FileLogger_Path"];
if (!string.IsNullOrEmpty(appSetting))
{
_path = appSetting;
}
int maxFileSize;
appSetting = ConfigurationManager.AppSettings["FileLogger_MaxFileSizeInMB"];
if (!string.IsNullOrEmpty(appSetting) && int.TryParse(appSetting, out maxFileSize))
{
_logFileSize = maxFileSize;
}
appSetting = ConfigurationManager.AppSettings["FileLogger_DefaultSeverity"];
if (!string.IsNullOrEmpty(appSetting))
{
try
{
_defaultSeverity = (LogSeverity)Enum.Parse(typeof(LogSeverity), appSetting);
}
catch (Exception ex)
{
WriteToEventLog(ex.Message);
}
}
}
// Clean up the stream writer
public void Dispose()
{
lock (_syncLock)
{
if (_writer != null)
{
_writer.Dispose();
}
}
}
// Write with default log severity
public void Write(string msg)
{
Write(_defaultSeverity, msg);
}
// Write message to log file
public void Write(LogSeverity logSeverity, string msg)
{
lock (_syncLock)
{
try
{
if (_writer.BaseStream == null)
{
_writer = new StreamWriter(_absolutePath, true);
}
CheckFileSize();
_writer.WriteLine("[{0}] at {1} -- {2}",
logSeverity.ToString(), DateTime.Now.ToString(), msg);
_writer.Flush();
}
catch (Exception ex)
{
WriteToEventLog(ex.Message);
}
}
}
private void WriteToEventLog(string message)
{
string source = "Logger";
string log = "Application";
try
{
if (!EventLog.SourceExists(source))
EventLog.CreateEventSource(source, log);
EventLog.WriteEntry(source, message, EventLogEntryType.Error);
}
catch { }
}
}
Thursday, May 10, 2007
.NET Interface Serialization Issue
Interface can not be serialized or de-serialized. That's because interface is just a contract not detailed implementation. However, in some cases, we do want to expose interface so objects are not restricted by a specific type.
Look at a simple example:
Look at a simple example:
public interface IPriceableThe PromotionPackage contains a list of IPriceable objects (ProductA, ProductB etc.). Everything is okay except that an exception will be thrown when we serialize the PromotionPackage object:
{
double GetPrice();
}
[Serializable]
public class ProductA : IPriceable
{
public double GetPrice()
{
return 123.345;
}
}
[Serializable]
public class ProductB : IPriceable
{
public double GetPrice()
{
return 234.567;
}
}
[Serializable]
public class PromotionPackage
{
public List<IPriceable> Items = new List<IPriceable>();
double GetPromotePrice()
{
//return PromotionCaculator.GetPrice(_items);
return 567.89; ;
}
}
{"Cannot serialize member PromotionPackage.Items of type IPriceable because it is an interface."}A workaround would be wrapping the interface to an abstract class:using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
public class Program
{
public interface IPriceable
{
double GetPrice();
}
[XmlInclude(typeof(ProductA))]
[XmlInclude(typeof(ProductB))]
public abstract class ProductBase : IPriceable
{
public abstract double GetPrice();
}
[Serializable]
public class ProductA : ProductBase
{
public override double GetPrice()
{
return 123.345;
}
}
[Serializable]
public class ProductB : ProductBase
{
public override double GetPrice()
{
return 234.567;
}
}
[Serializable]
public class PromotionPackage
{
public List<ProductBase> Items = new List<ProductBase>();
double GetPromotePrice()
{
//return PromotionCaculator.GetPrice(_items);
return 567.89; ;
}
}
static void Main(string[] args)
{
PromotionPackage package = new PromotionPackage();
package.Items.Add(new ProductA());
package.Items.Add(new ProductB());
string xmlString = string.Empty;
XmlSerializer serializer = new XmlSerializer(typeof(PromotionPackage));
// Serialize
using (StringWriter sw = new StringWriter())
{
serializer.Serialize(sw, package);
xmlString = sw.ToString();
}
// De-serialize
PromotionPackage newPackage = null;
using (StringReader sr = new StringReader(xmlString))
{
XmlTextReader reader = new XmlTextReader(sr);
newPackage = (PromotionPackage)serializer.Deserialize(reader);
}
Console.WriteLine("Done.");
Console.Read();
}
}
Saturday, May 05, 2007
A Managed Way To Serialize & Deserialze DateTime field
public class MyEventHere TimeString is exposed as a public property. Can we make it private? The answer is yes, but it requires the DataContract new in .NET 3.0:
{
public string Name;
[XmlIgnore]
public DateTime Time;
[XmlElement("Time")]
public string TimeString
{
get { return Time.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); }
set { Time = DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); }
}
public MyEvent() { }
public MyEvent(string name, DateTime time)
{
Name = name;
Time = time;
}
static void Main(string[] args)
{
MyEvent evt = new MyEvent("event1", new DateTime(2007, 5, 5));
XmlSerializer serializer = new XmlSerializer(typeof(MyEvent));
TextWriter writer = new StreamWriter(@"c:\event.xml");
serializer.Serialize(writer, evt);
writer.Close();
}
}
[DataContract]
public class MyEvent
{
[DataMember]
public string Name;
public DateTime Time;
[DataMember(Name="Time") ]
private string TimeString
{
get { return Time.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); }
set { Time = DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); }
}
public MyEvent(string name, DateTime time)
{
Name = name;
Time = time;
}
static void Main(string[] args)
{
MyEvent evt = new MyEvent("event1", new DateTime(2007, 5, 5));
DataContractSerializer serializer = new DataContractSerializer(typeof(MyEvent));
XmlWriter writer = XmlWriter.Create(@"c:\event.xml");
serializer.WriteObject(writer, evt);
writer.Close();
}
}
Monday, April 16, 2007
.NET Class Library Get Configuration File
A class library is used by different kind of applications, like a test console, a WinForm or Web applications. And the class library needs to read a configuration file at run time. How to do that? The common way is copy the configuration file to the folder where the class library assembly is sitting. Then the configuration file can be located by reflection at run time. But this doesn't work in ASP.NET web site environment because of the .NET dynamic compilation process. One resolution is to handle the path differently in web environment. Let's say if We have a convention of copying the configuration file to the root folder of web site, then the code to retrieve the full path of configuration file is like:
public static string GetConfigurationFile(string fileName)(Updated 2008/2) Another easy way to get configuration file is using AppDomain.CurrentDomain.SetupInformation.ConfigurationFile property which maps to app.config for Cosole/WinForm applications, and maps to web.config in ASP.NET.
{
string asmLocation = Assembly.GetExecutingAssembly().Location;
string asmFolder = asmLocation.Substring(0, asmLocation.LastIndexOf(@"\"));
string fileFullPath = asmFolder + @"\" + fileName;
// Fix the temporary folder issue when running in web environment
if (fileFullPath.Contains(@"\Microsoft.NET\Framework"))
{
fileFullPath = System.Web.Hosting.HostingEnvironment.MapPath("/") + fileName;
}
return fileFullPath;
}
Tuesday, April 03, 2007
StringDictionary, NameValueCollection, Dictionary<TKey, TValue> and KeyedCollection<TKey, TItem>
How to store key/value data in .NET?
DictionaryEntry in .NET 1.0 and generic KeyValuePair<TKey, TValue> in .NET 2.0 both store key/value pair inside a struct (not class). How about many KeyValue pairs? One solution is to put Key/value objects inside a collection like Generic List. But there's a major problem of such use: search by key is expensive and you need to loop through the whole collection. HashTable is fast in search but it's limited to string/object pair, and it's not strongly typed.
Are there any out-of-box strongly-typed collections for key/value objects?
If the key and value both are strings, StringDictionary and NameValueCollection are options. They have been available since .NET 1.0 and both are under System.Collections.Specialized name space. The difference is that NameValueCollection can have duplicate keys (multiple values for the same key are allowed).
In .NET 2.0 we have generic Dictionary<TKey, TValue> that is efficient and easy to use. More importantly the key and value objects can be of any types, and are strongly typed. But one issue of generic dictionary is that it can not be serialized directly. Paul Welter provided a SerializableDictionary solution for the problem but it's just a workaround. The other drawback of generic dictionary is that index accessing is not supported.
Generic KeyedCollection<TKey, TItem> under System.Collections.ObjectModel name space does not have those two issues on generic dictionary. KeyedCollection<TKey, TItem> supports index and key search, just like a hybrid version of generic List and Dictionary. But KeyedCollection<TKey, TItem> is just an abstract class, and you have to inherit from it and implement at least one abstract method to tell what's the key in each object inside the collection:
DictionaryEntry in .NET 1.0 and generic KeyValuePair<TKey, TValue> in .NET 2.0 both store key/value pair inside a struct (not class). How about many KeyValue pairs? One solution is to put Key/value objects inside a collection like Generic List. But there's a major problem of such use: search by key is expensive and you need to loop through the whole collection. HashTable is fast in search but it's limited to string/object pair, and it's not strongly typed.
Are there any out-of-box strongly-typed collections for key/value objects?
If the key and value both are strings, StringDictionary and NameValueCollection are options. They have been available since .NET 1.0 and both are under System.Collections.Specialized name space. The difference is that NameValueCollection can have duplicate keys (multiple values for the same key are allowed).
In .NET 2.0 we have generic Dictionary<TKey, TValue> that is efficient and easy to use. More importantly the key and value objects can be of any types, and are strongly typed. But one issue of generic dictionary is that it can not be serialized directly. Paul Welter provided a SerializableDictionary solution for the problem but it's just a workaround. The other drawback of generic dictionary is that index accessing is not supported.
Generic KeyedCollection<TKey, TItem> under System.Collections.ObjectModel name space does not have those two issues on generic dictionary. KeyedCollection<TKey, TItem> supports index and key search, just like a hybrid version of generic List and Dictionary. But KeyedCollection<TKey, TItem> is just an abstract class, and you have to inherit from it and implement at least one abstract method to tell what's the key in each object inside the collection:
protected abstract TKey GetKeyForItem(TItem item);Following is a demo of how to use KeyedCollection, where the EmployeeCollection uses employee number as a key:
using System;
using System.Text;
using System.Collections.ObjectModel;
class Program
{
static void Main()
{
EmployeeCollection ec = new EmployeeCollection();
ec.Add(new Employee("123456", "Rob", 40));
ec.Add(new Employee("234567", "Steve", 45));
bool contained = ec.Contains("123456");
ec["123456"].Age = 44;
ec[1].Age = 45;
Console.Read();
}
public class Employee
{
public Employee(string empNumber, string name, int age)
{
EmployeeNumber = empNumber;
Name = name;
Age = age;
}
public string EmployeeNumber;
public string Name;
public int Age;
// other properties and memebers
}
public class EmployeeCollection : KeyedCollection<string, Employee>
{
protected override string GetKeyForItem(Employee item)
{
return item.EmployeeNumber;
}
}
}
Tag:
Practices
Subscribe to:
Posts (Atom)