Scanning a Network for Computers in C#

I was tasked recently with creating an app that would scan a network for any and all computers in it using C#. Well not exactly... It was more anything in our domain, but for the most part that should be anything on our network. Anyway, it turns out that it's extremely simple and really only took 3 steps:

  • Get the list of computer names from Active Directory
  • Do a DNS lookup on those names
  • And report the results

That's all that I needed to do. It was especially easy because of some helper classes that I had laying around to deal with AD. A while back I posted two classes to help with AD. They're a bit out of date as I've added extra functionality since then, the newer version of the classes looks like this:

/*
Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/


#region Usings
using System;
using System.Collections.Generic;
using System.DirectoryServices;
#endregion

namespace Utilities.LDAP
{
    /// <summary>
    /// Class for helping with AD
    /// </summary>
    public class Directory:IDisposable
    {
        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="UserName">User name used to log in</param>
        /// <param name="Password">Password used to log in</param>
        /// <param name="Path">Path of the LDAP server</param>
        /// <param name="Query">Query to use in the search</param>
        public Directory(string Query,string UserName, string Password, string Path)
        {
            try
            {
                Entry = new DirectoryEntry(Path, UserName, Password, AuthenticationTypes.Secure);
                this.Path = Path;
                this.UserName = UserName;
                this.Password = Password;
                this.Query = Query;
                Searcher = new DirectorySearcher(Entry);
                Searcher.Filter = Query;
                Searcher.PageSize = 1000;
            }
            catch { throw; }
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Finds a user by his user name
        /// </summary>
        /// <param name="UserName">User name to search by</param>
        /// <returns>The user's entry</returns>
        public Entry FindUserByUserName(string UserName)
        {
            try
            {
                List<Entry> Entries = FindUsers("samAccountName=" + UserName);
                if (Entries.Count > 0)
                {
                    return Entries[0];
                }
                return null;
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all active users
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all active users' entries</returns>
        public List<Entry> FindActiveUsers(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&((userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$)))({0}))", Filter);
                return FindUsers(Filter);
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all users
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all users meeting the specified Filter</returns>
        public List<Entry> FindUsers(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&(objectClass=User)(objectCategory=Person)({0}))", Filter);
                Searcher.Filter = Filter;
                return FindAll();
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all computers
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all computers meeting the specified Filter</returns>
        public List<Entry> FindComputers(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&(objectClass=computer)({0}))", Filter);
                Searcher.Filter = Filter;
                return FindAll();
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all active users and groups
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all active groups' entries</returns>
        public List<Entry> FindActiveUsersAndGroups(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&((userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$)))({0}))", Filter);
                return FindUsersAndGroups(Filter);
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all users and groups
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all users and groups meeting the specified Filter</returns>
        public List<Entry> FindUsersAndGroups(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&(|(&(objectClass=Group)(objectCategory=Group))(&(objectClass=User)(objectCategory=Person)))({0}))", Filter);
                Searcher.Filter = Filter;
                return FindAll();
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all active groups
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all active groups' entries</returns>
        public List<Entry> FindActiveGroups(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&((userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$)))({0}))", Filter);
                return FindGroups(Filter);
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all groups
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all groups meeting the specified Filter</returns>
        public List<Entry> FindGroups(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&(objectClass=Group)(objectCategory=Group)({0}))", Filter);
                Searcher.Filter = Filter;
                return FindAll();
            }
            catch { throw; }
        }

        /// <summary>
        /// Returns a group's list of members
        /// </summary>
        /// <param name="GroupName">The group's name</param>
        /// <returns>A list of the members</returns>
        public List<Utilities.LDAP.Entry> FindActiveGroupMembers(string GroupName)
        {
            try
            {
                List<Utilities.LDAP.Entry> Entries = this.FindGroups("cn=" + GroupName);
                if (Entries.Count < 1)
                    return new List<Utilities.LDAP.Entry>();

                return this.FindActiveUsersAndGroups("memberOf=" + Entries[0].DistinguishedName);
            }
            catch
            {
                return new List<Utilities.LDAP.Entry>();
            }
        }

        /// <summary>
        /// Finds all entries that match the query
        /// </summary>
        /// <returns>A list of all entries that match the query</returns>
        public List<Entry> FindAll()
        {
            try
            {
                List<Entry> ReturnedResults = new List<Entry>();
                SearchResultCollection Results = Searcher.FindAll();
                foreach (SearchResult Result in Results)
                {
                    ReturnedResults.Add(new Entry(Result.GetDirectoryEntry()));
                }
                Results.Dispose();
                return ReturnedResults;
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds one entry that matches the query
        /// </summary>
        /// <returns>A single entry matching the query</returns>
        public Entry FindOne()
        {
            try
            {
                SearchResult Result = Searcher.FindOne();
                return new Entry(Result.GetDirectoryEntry());
            }
            catch { throw; }
        }

        /// <summary>
        /// Closes the directory
        /// </summary>
        public void Close()
        {
            try
            {
                Entry.Close();
            }
            catch { throw; }
        }

        /// <summary>
        /// Checks to see if the person was authenticated
        /// </summary>
        /// <returns>true if they were authenticated properly, false otherwise</returns>
        public bool Authenticate()
        {
            try
            {
                if (!Entry.Guid.ToString().ToLower().Trim().Equals(""))
                {
                    return true;
                }
            }
            catch
            {
            }
            return false;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Path of the server
        /// </summary>
        public string Path
        {
            get { return _Path; }
            set
            {
                _Path = value;
                try
                {
                    if (Entry != null)
                    {
                        Entry.Close();
                    }
                    Entry = new DirectoryEntry(_Path, _UserName, _Password, AuthenticationTypes.Secure);
                    Searcher = new DirectorySearcher(Entry);
                    Searcher.Filter = Query;
                    Searcher.PageSize = 1000;
                }
                catch { throw; }
            }
        }

        /// <summary>
        /// User name used to log in
        /// </summary>
        public string UserName
        {
            get { return _UserName; }
            set
            {
                _UserName = value;
                try
                {
                    if (Entry != null)
                    {
                        Entry.Close();
                    }
                    Entry = new DirectoryEntry(_Path, _UserName, _Password, AuthenticationTypes.Secure);
                    Searcher = new DirectorySearcher(Entry);
                    Searcher.Filter = Query;
                    Searcher.PageSize = 1000;
                }
                catch { throw; }
            }
        }

        /// <summary>
        /// Password used to log in
        /// </summary>
        public string Password
        {
            get { return _Password; }
            set
            {
                _Password = value;
                try
                {
                    if (Entry != null)
                    {
                        Entry.Close();
                    }
                    Entry = new DirectoryEntry(_Path, _UserName, _Password, AuthenticationTypes.Secure);
                    Searcher = new DirectorySearcher(Entry);
                    Searcher.Filter = Query;
                    Searcher.PageSize = 1000;
                }
                catch { throw; }
            }
        }

        /// <summary>
        /// The query that is being made
        /// </summary>
        public string Query
        {
            get { return _Query; }
            set
            {
                _Query = value;
                Searcher.Filter = _Query;
            }
        }

        /// <summary>
        /// Decides what to sort the information by
        /// </summary>
        public string SortBy
        {
            get { return _SortBy; }
            set
            {
                _SortBy = value;
                Searcher.Sort.PropertyName = _SortBy;
                Searcher.Sort.Direction = SortDirection.Ascending;
            }
        }
        #endregion

        #region Private Variables
        private string _Path = "";
        private string _UserName = "";
        private string _Password = "";
        private DirectoryEntry Entry = null;
        private string _Query = "";
        private DirectorySearcher Searcher = null;
        private string _SortBy = "";
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (Entry != null)
            {
                Entry.Dispose();
                Entry = null;
            }
            if (Searcher != null)
            {
                Searcher.Dispose();
                Searcher = null;
            }
        }

        #endregion
    }
}

/*
Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/


#region Usings
using System;
using System.Collections.Generic;
using System.DirectoryServices;
#endregion

namespace Utilities.LDAP
{
    /// <summary>
    /// Directory entry class
    /// </summary>
    public class Entry:IDisposable
    {
        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="DirectoryEntry">Directory entry for the item</param>
        public Entry(DirectoryEntry DirectoryEntry)
        {
            this._DirectoryEntry = DirectoryEntry;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Actual base directory entry
        /// </summary>
        public DirectoryEntry DirectoryEntry
        {
            get { return _DirectoryEntry; }
            set { _DirectoryEntry = value; }
        }

        /// <summary>
        /// Email property for this entry
        /// </summary>
        public string Email
        {
            get { return (string)GetValue("mail"); }
            set { SetValue("mail", value); }
        }

        /// <summary>
        /// distinguished name property for this entry
        /// </summary>
        public string DistinguishedName
        {
            get { return (string)GetValue("distinguishedname"); }
            set { SetValue("distinguishedname", value); }
        }

        /// <summary>
        /// country code property for this entry
        /// </summary>
        public string CountryCode
        {
            get { return (string)GetValue("countrycode"); }
            set { SetValue("countrycode", value); }
        }

        /// <summary>
        /// company property for this entry
        /// </summary>
        public string Company
        {
            get { return (string)GetValue("company"); }
            set { SetValue("company", value); }
        }

        /// <summary>
        /// MemberOf property for this entry
        /// </summary>
        public List<string> MemberOf
        {
            get
            {
                List<string> Values = new List<string>();
                PropertyValueCollection Collection = DirectoryEntry.Properties["memberof"];
                foreach (object Item in Collection)
                {
                    Values.Add((string)Item);
                }
                return Values;
            }
        }

        /// <summary>
        /// display name property for this entry
        /// </summary>
        public string DisplayName
        {
            get { return (string)GetValue("displayname"); }
            set { SetValue("displayname", value); }
        }

        /// <summary>
        /// initials property for this entry
        /// </summary>
        public string Initials
        {
            get { return (string)GetValue("initials"); }
            set { SetValue("initials", value); }
        }

        /// <summary>
        /// title property for this entry
        /// </summary>
        public string Title
        {
            get { return (string)GetValue("title"); }
            set { SetValue("title", value); }
        }

        /// <summary>
        /// samaccountname property for this entry
        /// </summary>
        public string SamAccountName
        {
            get { return (string)GetValue("samaccountname"); }
            set { SetValue("samaccountname", value); }
        }

        /// <summary>
        /// givenname property for this entry
        /// </summary>
        public string GivenName
        {
            get { return (string)GetValue("givenname"); }
            set { SetValue("givenname", value); }
        }

        /// <summary>
        /// cn property for this entry
        /// </summary>
        public string CN
        {
            get { return (string)GetValue("cn"); }
            set { SetValue("cn", value); }
        }

        /// <summary>
        /// name property for this entry
        /// </summary>
        public string Name
        {
            get { return (string)GetValue("name"); }
            set { SetValue("name", value); }
        }

        /// <summary>
        /// office property for this entry
        /// </summary>
        public string Office
        {
            get { return (string)GetValue("physicaldeliveryofficename"); }
            set { SetValue("physicaldeliveryofficename", value); }
        }

        /// <summary>
        /// telephone number property for this entry
        /// </summary>
        public string TelephoneNumber
        {
            get { return (string)GetValue("telephonenumber"); }
            set { SetValue("telephonenumber", value); }
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Saves any changes that have been made
        /// </summary>
        public void Save()
        {
            try
            {
                _DirectoryEntry.CommitChanges();
            }
            catch { throw; }
        }

        /// <summary>
        /// Gets a value from the entry
        /// </summary>
        /// <param name="Property">Property you want the information about</param>
        /// <returns>an object containing the property's information</returns>
        public object GetValue(string Property)
        {
            try
            {
                PropertyValueCollection Collection = DirectoryEntry.Properties[Property];
                if (Collection != null)
                {
                    return Collection.Value;
                }
                return null;
            }
            catch { throw; }
        }

        /// <summary>
        /// Sets a property of the entry to a specific value
        /// </summary>
        /// <param name="Property">Property of the entry to set</param>
        /// <param name="Value">Value to set the property to</param>
        public void SetValue(string Property,object Value)
        {
            try
            {
                PropertyValueCollection Collection = DirectoryEntry.Properties[Property];
                if (Collection != null)
                {
                    Collection.Value = Value;
                }
            }
            catch { throw; }
        }
        #endregion

        #region Private Variables
        private DirectoryEntry _DirectoryEntry;
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_DirectoryEntry != null)
            {
                _DirectoryEntry.Dispose();
                _DirectoryEntry = null;
            }
        }

        #endregion
    }
}

The two classes are Directory and Entry. Directory is what is used to connect to Active Directory and the Entry class actually holds each entry's info (also note that they are IDisposable, so make sure to dispose of them when done). Anyway, most of the code is of little use in this instance. I mean the directory class has the ability to search for users, groups, etc. There's really only one function of much use: FindComputers. This function allows us to search for any computer that fits specific criteria that we pass into it. Or in our instance, anything in our domain:

    using(Utilities.LDAP.Directory TempDirectory = new Utilities.LDAP.Directory("", "USERNAME", "PASSWORD", "LDAPLOCATION"))
    {
        List<Utilities.LDAP.Entry> Entries = TempDirectory.FindComputers("name=*");
    }

The code above uses the Directory class to find all computers within AD, giving us back a list of those entries. This isn't the most useful code at this point but it gets us pass step one. We have our list of machines. So on to step 2, doing the DNS lookup. One of the great things about .Net is that a lot of functionality is already there for you. In this case all we have to do is take the name of each entry and feed it to Dns.GetHostEntry (which is in the System.Net namespace). It in turn gives us an IPAddress class, which holds every address that the machine is using (IPv4, IPv6, etc.). However, if you've tried it in the past you will notice that if it can't find the machine, it takes forever to run (well 14 seconds anyway). And if you're searching for 5000 machines and any decent number of them are offline, that is going to, well, suck. There are options, you can either modify your registry or you can use multithreading to get the job done. I went with the second option:

                    using(Utilities.LDAP.Directory TempDirectory = new Utilities.LDAP.Directory("", "USERNAME", "PASSWORD", "LDAPLOCATION"))
                    {
                        List<Utilities.LDAP.Entry> Entries = TempDirectory.FindComputers("name=*");
                        for (int x = 0; x < Entries.Count; ++x)
                        {
                            try
                            {
                                using (Utilities.LDAP.Entry Entry = Entries[x])
                                {
                                    DNSSearch TempWorker = new DNSSearch(Entry.Name);
                                    TempWorker.Finished = new EventHandler<Utilities.Events.EventArgs.OnEndEventArgs>(FinishedSearch);
                                    TempWorker.Start();
                                }
                            }
                            catch { }
                        }
                    }

The code above just adds to our AD search, adding a loop, creating a DNSSearch class for each item, passing in the name, setting an eventhandler, and calling start. The DNSSearch class looks like this:

    public class DNSSearch:Worker<Machine,string>
    {
        public DNSSearch(string Params)
            : base(Params)
        {
        }

        protected override Machine Work(string Params)
        {
            Machine TempMachine = new Machine();
            TempMachine.Name = Params;
            TempMachine.Addresses = new List<string>();
            try
            {
                IPHostEntry HostEntry = Dns.GetHostEntry(Params);
                foreach (IPAddress Address in HostEntry.AddressList)
                {
                    TempMachine.Addresses.Add(Address.AddressFamily + ": " + Address.ToString());
                }
            }
            catch { }
            return TempMachine;
        }
    }

The class inherits from a Worker class, which I showed here. Although there are some slight updates to it, so you may want to get the latest version from my utility library here. Anyway, the base class wants to know what we are getting as input and what to expect as output. In our case it's expecting a string for input and a class called Machine for output. The machine class looks like this:

    public class Machine
    {
        public string Name { get; set; }
        public List<string> Addresses { get; set; }

    }

It's really just a holder for the info. So when we call Start on the DNSSearch object, back in the original function, the base class starts the thread and calls Work in DNSSearch. The Work function creates a Machine object, sets the name of the machine and calls Dns.GetHostEntry, filling out the address info for us. When it's done it returns the machine object. This gets sent to our Finished EventHandler. In the original function we set the Finished event to a function called FinishedSearch:

        public static List<Machine> MachineList { get; set; }

        public static void FinishedSearch(object sender, Utilities.Events.EventArgs.OnEndEventArgs e)
        {
            lock (typeof(List<Machine>))
            {
                if (MachineList == null)
                    MachineList = new List<Machine>();
                MachineList.Add((Machine)e.Content);
            }
        }

The function simply locks our list of machines, sets it up if need be and adds our machine object to said list and then unlocks it. So how do we get this info back to our form, or web page, or whatever? Simple:

        public static List<Machine> SearchNetwork()
        {
            try
            {
                if (MachineList == null)
                {
                    using(Utilities.LDAP.Directory TempDirectory = new Utilities.LDAP.Directory("", "USERNAME", "PASSWORD", "LDAPLOCATION"))
                    {
                        List<Utilities.LDAP.Entry> Entries = Temp.FindComputers("name=*");
                        for (int x = 0; x < Entries.Count; ++x)
                        {
                            try
                            {
                                using (Utilities.LDAP.Entry Entry = Entries[x])
                                {
                                    DNSSearch TempWorker = new DNSSearch(Entry.Name);
                                    TempWorker.Finished = new EventHandler<Utilities.Events.EventArgs.OnEndEventArgs>(FinishedSearch);
                                    TempWorker.Start();
                                }
                            }
                            catch { }
                        }
                    }
                }
                return MachineList;
            }
            catch { throw; }
        }

By returning the MachineList, which was static, we have access to it as it's being updated. So we can do something like this in a function:

                List<Machine> Machines = SearchNetwork();
                lock (typeof(List<Machine>))
                {
                    foreach (Machine TempMachine in Machines)
                    {
                        if (TempMachine.Addresses.Count > 0)
                        {
                            //The machine is online
                        }
                        else
                        {
                            //The machine is offline
                        }
                    }
                }

Note that we have to lock it because the threads are going to continue to update our list, but it doesn't stop us from locking it and using it ourselves. An even better idea might be to use a list with events telling you when something is added... Sort of like the code I created here. Using something like that, you could have a list on a form update in real time as items are added to the list. Anyway, I know I threw a bunch of code up here but it's quite simple once you get down to it. Anyway, try it out, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 5/3/2010 at 2:50 PM
Tags: , , , ,
Categories: C#
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

Multithreading in C#

Multithreading seems to be a hot topic of discussion for a lot of programmers. It also seems to be one of the more difficult concepts to grasp (or even to get started with). Luckily in C#/.Net it's pretty simple when compared to some other languages that I've dealt with. In C# all you need to do is make a call to ThreadPool or create a Thread class and you're done... Sort of anyway... However there is a lack of certain functionality that you may want to add. In my case I needed a way of being notified of certain events so I created a helper base class that is reusable.

/// <summary>
    /// Worker class used in multi threading
    /// </summary>
    /// <typeparam name="ResultType">Result type</typeparam>
    /// <typeparam name="InputParams">The input parameter type</typeparam>
    public abstract class Worker<ResultType,InputParams>
    {
        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="Params">Parameters used in the function</param>
        protected Worker(InputParams Params)
        {
            this.Params = Params;
            this.WorkerThread = new Thread(DoWork);
        }

        #endregion

        #region Protected Abstract Functions

        /// <summary>
        /// Function that actually does the work
        /// </summary>
        /// <param name="Params">Parameter used by the function</param>
        /// <returns>The result of the function</returns>
        protected abstract ResultType Work(InputParams Params);

        #endregion

        #region Public Functions

        /// <summary>
        /// Starts the thread
        /// </summary>
        public void Start()
        {
            this.WorkerThread.Start();
        }

        /// <summary>
        /// Stops the thread and waits for it to finish
        /// </summary>
        public void Stop()
        {
            if (WorkerThread != null && WorkerThread.IsAlive)
            {
                this.WorkerThread.Join();
                this.WorkerThread = null;
            }
            OnEndEventArgs EndEvents = new OnEndEventArgs();
            EndEvents.Content = Result;
            EventHelper.Raise<OnEndEventArgs>(Finished, this, EndEvents);
        }

        #endregion

        #region Private Functions

        /// <summary>
        /// Wrapper for the function that actually does the work.
        /// Calls the start and finished events as well as stores
        /// the result for use within the class.
        /// </summary>
        private void DoWork()
        {
            EventHelper.Raise<OnStartEventArgs>(Started, this, new OnStartEventArgs());

            Result = Work(this.Params);

            OnEndEventArgs EndEvents = new OnEndEventArgs();
            EndEvents.Content = Result;
            EventHelper.Raise<OnEndEventArgs>(Finished, this, EndEvents);
        }

        #endregion

        #region Events

        /// <summary>
        /// Called when the thread is finished
        /// </summary>
        public EventHandler<OnEndEventArgs> Finished { get; set; }

        /// <summary>
        /// Called when the thread is started
        /// </summary>
        public EventHandler<OnStartEventArgs> Started { get; set; }

        /// <summary>
        /// Can be used by the worker function to indicate progress
        /// </summary>
        public EventHandler<ChangedEventArgs> Updated { get; set; }

        /// <summary>
        /// Can be used by the worker function to indicate an exception has occurred
        /// </summary>
        public EventHandler<OnErrorEventArgs> Exception { get; set; }

        #endregion

        #region Properties

        /// <summary>
        /// Indicates whether or not the thread is stopped/started
        /// </summary>
        public bool Stopped
        {
            get
            {
                if (WorkerThread != null && WorkerThread.IsAlive)
                    return false;
                return true;
            }
        }

        /// <summary>
        /// The result (can be used by the class that inherits from this base class
        /// </summary>
        protected ResultType Result { get; set; }

        /// <summary>
        /// Parameters used in the function
        /// </summary>
        private InputParams Params = default(InputParams);

        /// <summary>
        /// The thread used
        /// </summary>
        private Thread WorkerThread=null;

        #endregion
    }

The code should be fairly easy to follow. You use this as a base class adding the result and parameter type. There is one function that you need to override. That's it really, well with one exception. The class has events that it uses (which uses EventArgs that are within my utility library). It has Started and Finished which is called when the thread is started and stopped respectively. The Finished event will contain the results from the function within the Content object.There are also events for Updated and Exception which are optional. The Updated event would be used to update the rest of the world on the thread's progress. The Exception event on the other hand would be used when the thread has an exception, letting everyone else know. Anyway, I hope that the code can be of use to someone. So try it out, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 10/7/2009 at 2:02 PM
Tags: , ,
Categories: C#
Post Information: Permalink | Comments (1) | Post RSSRSS comment feed

Vector Class and Events in .Net

If you came to C# from C++, you're going to notice rather fast that one data type is missing from the System.Collections namespace. Namely a vector class. In C++ there's the std::vector class which is used about any time that you need an array in C++ (unless of course you like recreating an array when you out grow it). In C#, there is no class like that. We have the List class, which is used with about the same frequency and has a similar function but you don't have that great of control over the capacity. I can set the capacity of it but I can't really control how much is added each time it needs to resize (not to mention I would prefer to have some sort of tie in with events, etc.). So I ended up creating my own Vector class.

/*
Copyright (c) 2009 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/

#region Usings
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utilities.Events.EventArgs;
using Utilities.Events;
#endregion

namespace Utilities.DataTypes
{
    /// <summary>
    /// Vector class
    /// </summary>
    /// <typeparam name="T">The type of item the vector should hold</typeparam>
    public class Vector<T> : IList<T>
    {
        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public Vector()
        {
            Items = new T[DefaultSize];
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="InitialSize">Initial size of the vector</param>
        public Vector(int InitialSize)
        {
            if (InitialSize < 1) throw new ArgumentOutOfRangeException("InitialSize");

            Items = new T[InitialSize];
            DefaultSize = InitialSize;
        }

        #endregion

        #region IList<T> Members

        public int IndexOf(T item)
        {
            return Array.IndexOf<T>(this.Items, item, 0, this.NumberItems);
        }

        public void Insert(int index, T item)
        {
            if (index > this.NumberItems || index < 0) throw new ArgumentOutOfRangeException("index");

            try
            {
                if (this.NumberItems == this.Items.Length)
                {
                    Array.Resize<T>(ref this.Items, this.Items.Length * 2);
                }
                if (index < this.NumberItems)
                {
                    Array.Copy(this.Items, index, this.Items, index + 1, this.NumberItems - index);
                }
                this.Items[index] = item;
                ++this.NumberItems;
                EventHelper.Raise<ChangedEventArgs>(Changed, this, new ChangedEventArgs());
            }
            catch (Exception a) { throw a; }
        }

        public void RemoveAt(int index)
        {
            if (index > this.NumberItems || index < 0) throw new ArgumentOutOfRangeException("index");

            try
            {
                if (index < this.NumberItems)
                {
                    Array.Copy(this.Items, index + 1, this.Items, index, this.NumberItems - (index + 1));
                }
                this.Items[this.NumberItems - 1] = default(T);
                --this.NumberItems;
                EventHelper.Raise<ChangedEventArgs>(Changed, this, new ChangedEventArgs());
            }
            catch (Exception a) { throw a; }
        }

        public T this[int index]
        {
            get
            {
                if (index > this.NumberItems || index < 0) throw new ArgumentOutOfRangeException("index");
                return this.Items[index];
            }
            set
            {
                if (index > this.NumberItems || index < 0) throw new ArgumentOutOfRangeException("index");
                this.Items[index] = value;
                EventHelper.Raise<ChangedEventArgs>(Changed, this, new ChangedEventArgs());
            }
        }

        #endregion

        #region ICollection<T> Members

        public void Add(T item)
        {
            try
            {
                Insert(this.NumberItems, item);
            }
            catch (Exception a) { throw a; }
        }

        public void Clear()
        {
            try
            {
                Array.Clear(this.Items, 0, this.Items.Length);
                this.NumberItems = 0;
                EventHelper.Raise<ChangedEventArgs>(Changed, this, new ChangedEventArgs());
            }
            catch (Exception a) { throw a; }
        }

        public bool Contains(T item)
        {
            return (this.IndexOf(item) >= 0);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            Array.Copy(this.Items, 0, array, arrayIndex, this.NumberItems);
        }

        public int Count
        {
            get { return this.NumberItems; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(T item)
        {
            int Index = this.IndexOf(item);
            if (Index > 0)
            {
                this.RemoveAt(Index);
                return true;
            }
            return false;
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            for (int x = 0; x < this.NumberItems; ++x)
            {
                yield return this.Items[x];
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            for (int x = 0; x < this.NumberItems; ++x)
            {
                yield return this.Items[x];
            }
        }

        #endregion

        #region Protected Variables/Properties

        protected int DefaultSize = 2;
        protected T[] Items = null;
        protected int NumberItems { get; set; }

        #endregion

        #region Events
        public EventHandler<ChangedEventArgs> Changed;
        #endregion
    }
}

Most of the class above is pretty straight forward. We have an array of elements and each time we insert an item and need to increase the size of the array, we double its size. If you wanted you could switch it from multiplying by two to a fixed size (a simple change really). The only portion which may not jump out at you though is the fact that there is an EventHandler class in there. An EventHandler is basically a generic class that holds a method that handles an event. It's not mine, just a .Net class. But it's defined with a ChangedEventArgs class which is mine. The ChangedEventArgs class is a simple EventArgs class that looks like this:

    public class BaseEventArgs : System.EventArgs
    {
        public bool Stop
        {
            get { return _Stop; }
            set { _Stop = value; }
        }
        private bool _Stop = false;

        public object Content
        {
            get { return _Content; }
            set { _Content = value; }
        }
        private object _Content = null;
    }

    public class ChangedEventArgs : BaseEventArgs
    {
    }

Just a slightly modified EventArgs class. The other thing that you may notice about the vector class is it uses an EventHelper class to call the EventHandler. Once again, it's something that I created. Basically I was tired of writing the basic event raising code for every instance so I created a generic way of doing it.

    public static class EventHelper
    {
        /// <summary>
        /// Raises an event
        /// </summary>
        /// <typeparam name="T">The type of the event args</typeparam>
        /// <param name="Delegate">The delegate</param>
        /// <param name="Sender">The sender</param>
        /// <param name="EventArgs">The event args</param>
        public static void Raise<T>(EventHandler<T> Delegate, object Sender, T EventArg) where T : System.EventArgs
        {
            try
            {
                if (Delegate != null)
                {
                    Delegate(Sender, EventArg);
                }
            }
            catch { throw; }
        }
}

It's pretty simple. Just takes in an EventHandler, the sender, and the eventargs. Even if all it does is saves me a couple lines of code in a couple other classes, that's all I really need. Anyway, that's it. With those classes we have our Vector class that can notify us when things are changed, we control how it grows, etc. along with a couple other functions/classes that you might find useful. So try it out, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 8/27/2009 at 8:26 AM
Tags: , , , ,
Categories: C#
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed