August 23, 2011

Enum and Description Attribute

Found this helper in this article EnumHelper - Getting a Friendly Description from an Enum by Grant Barrington, initially.
Found idea for the generic enum class here
Have refactored and merged them a little and added a couple of my own methods
    /// <summary>
    /// Not really an extension class but this is more readable than an extension class.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public static class Enum<T> 
        where T : struct // Cannot specify an enum here so narrow it down to a struct
    {
        /// <summary>
        /// Parse the given string into an enum value, ignores the case of the string
        /// </summary>
        /// <param name="value">string to parse, leading and trailing whitespace characters are removed</param>
        /// <returns>Enum value parsed from the string</returns>
        public static T Parse(string value)
        {
            if (false == typeof(T).IsEnum)
                throw new NotSupportedException(typeof(T).Name + " must be an Enum");

            return Enum<T>.Parse(value.Trim(), true);
        }

        /// <summary>
        /// Parse the given string into an enum value
        /// </summary>
        /// <param name="value">string to parse, leading and trailing whitespace characters are removed</param>
        /// <param name="ignoreCase">Whether to ignore the case of the letters in the string when parsing</param>
        /// <returns>Enum value parsed from the string</returns>
        public static T Parse(string value, bool ignoreCase)
        {
            if (false == typeof(T).IsEnum)
                throw new NotSupportedException(typeof(T).Name + " must be an Enum");

            return (T)Enum.Parse(typeof(T), value.Trim(), ignoreCase);
        }

        /// <summary>
        /// Try to parse the given string into an enum value, ignores the case of the string. Capture any exceptions
        /// </summary>
        /// <param name="value">string to parse, leading and trailing whitespace characters are removed</param>
        /// <param name="returnedValue">Enum value parsed from the string</param>
        /// <returns>true if the string was successfully parsed into a enum value of the given type</returns>
        public static bool TryParse(string value, out T returnedValue)
        {
            return Enum.TryParse<T>(value, true, out returnedValue);
        }

        /// <summary>
        /// Try to parse the given string into an enum value. Capture any exceptions
        /// </summary>
        /// <param name="value">string to parse, leading and trailing whitespace characters are removed</param>
        /// <param name="ignoreCase">Whether to ignore the case of the letters in the string when parsing</param>
        /// <param name="returnedValue">Enum value parsed from the string</param>
        /// <returns>true if the string was successfully parsed into a enum value of the given type</returns>
        public static bool TryParse(string value, bool ignoreCase, out T returnedValue)
        {
            return Enum.TryParse<T>(value, ignoreCase, out returnedValue);
        }

        /// <summary>
        /// Get the enum values of the given enum type
        /// </summary>
        /// <returns>Array of the enum values in the enum type</returns>
        public static Array GetValues()
        {
            if (false == typeof(T).IsEnum)
                throw new NotSupportedException(typeof(T).Name + " must be an Enum");

            return Enum.GetValues(typeof(T));
        }

        /// <summary>
        /// Returns first description attribute of the enum value or the enum value 
        /// itself as a string if no description attribute was found
        /// </summary>
        /// <param name="enumValue">An enum value in the given enum type</param>
        /// <returns>
        /// First description attribute found of the enum value or the enum value 
        /// itself as a string if no description attribute was found
        /// </returns>
        public static string GetAsString(T enumValue)
        {
            if (false == typeof(T).IsEnum)
                throw new NotSupportedException(typeof(T).Name + " must be an Enum");

            string res = GetFirstDescriptionAttribute(enumValue);
            if (string.IsNullOrEmpty(res))
                res = enumValue.ToString();
            return res;
        }

        /// <summary>
        /// Returns first description attribute of the enum value
        /// </summary>
        /// <param name="enumValue">An enum value in the given enum type</param>
        /// <returns>
        /// First description attribute found of the enum value or null if no description attribute was found
        /// </returns>
        public static string GetDescriptionAttribute(T enumValue)
        {
            if (false == typeof(T).IsEnum)
                throw new NotSupportedException(typeof(T).Name + " must be an Enum");

            Type type = enumValue.GetType();

            MemberInfo[] memInfo = type.GetMember(enumValue.ToString());

            if (memInfo != null && memInfo.Length > 0)
            {
                object[] attrs = memInfo[0].GetCustomAttributes(
                    typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
                {
                    return ((DescriptionAttribute)attrs[0]).Description;
                }
            }

            return null;
        }

 
    }
Now use the above helpers to demonstrate how to get the description attribute on an enum entry and how to get the best string form for an enum for entry into a listbox or something
public enum DescriptionsEnum
{
    [DescriptionAttribute("Add On")]
    AddOn,
    [DescriptionAttribute("Snap On")]
    SnapOn = 7,
    Adjacent = 2,
    Fixed = 12,
};

public void Test1()
{
    int asInt = 0;            
    foreach (var entry in Enum<DescriptionsEnum>.GetValues())
    {
        asInt = (int)entry;
        Console.WriteLine(asInt.ToString() + " " + 
            entry.ToString() + ":" + 
            Enum<DescriptionsEnum>.GetDescriptionAttribute((Enum)entry));
    }
    Console.WriteLine();
    foreach (var entry in Enum<DescriptionsEnum>.GetValues())
    {
        asInt = (int)entry;
        Console.WriteLine(asInt.ToString() + " " + 
            Enum<DescriptionsEnum>.GetAsString((Enum)entry));
    }
}
and the output of the test is
0 AddOn:Add On
2 Adjacent:
7 SnapOn:Snap On
12 Fixed:

0 Add On
2 Adjacent
7 Snap On
12 Fixed
There is also an interesting article on Enum: Binding to the Description Attribute by Deborah Kurata.
Using these helpers making something to bind to is much simpler:
Dictionary<string, int> bindToMe = new Dictionary<string, int>();
foreach (var entry in Enum.GetValues(typeof(DescriptionsEnum)))
{
    asInt = (int)entry;
    bindToMe.Add(EnumHelper.GetAsString((Enum)entry), asInt);
} 

August 22, 2011

Properties in C++/CLI

See also C++/CLI lesson here
To implement them completely inline:
virtual property bool IsWritable {
  bool get() { return isWritable_; };
  void set(bool value) { isWritable_ = value; };
}
In header file (.h) for a header+source implementation:
virtual property System::String ^Name {
  System::String ^get();
  void set(System::String^); 
}
then in the source (.cpp) file:
System::String ^ XXX::Name::get() {
  return gcnew System::String(xxx.getName().c_str());
}

void XXX::Name::set(System::String ^value) {
   ...
}
To override, in the header of derived class change the definition with an override statement:
virtual property bool DoSomething {
  bool get() override;
  bool set() override;
}

August 19, 2011

FileSystemWatcher

File system watcher hints and tips
Msdn page for it has some useful information on gotchas

Following sample code watches a file and fires events when the file "appears" or "disappears". In this case the file could "appear" when it is created or when it is renamed from an existing file and "disappear" if it is deleted or the file is renamed to something else.

This renaming issue is something to watch out for when using this class! Detecting this is a bit more subtle.
//class fields
FileSystemWatcher fileWatcher = new FileSystemWatcher();
// Target a specific file (in this case)
readonly string targetFileName = "Myfile.name";
string targetDirectory;
string fullTargetFilePath;
...
// Expose some events
public event EventHandler LockFileAppeared;
public event EventHandler LockFileDisappeared;

// Private Firing events
void FireLockFileAppeared(FileSystemEventArgs eargs)
{
  if (LockFileAppeared != null)
    LockFileAppeared(this, eargs);
}

void FireLockFileDisappeared(FileSystemEventArgs eargs)
{
  if (LockFileDisappeared != null)
    LockFileDisappeared(this, eargs);
}
...
// Initialise it
void InitialiseFileWatcher()
{
  if (Directory.Exists(this.targetDirectory))
  {
    this.fullTargetFilePath = System.IO.Path.Combine(
            LockFileDirectory, LockFileName);
    fileWatcher.Path = LockFileDirectory;
    fileWatcher.Filter = LockFileName; 
    fileWatcher.IncludeSubdirectories = false;
    fileWatcher.Renamed += new RenamedEventHandler(fileWatcher_Renamed);
    fileWatcher.Error += new ErrorEventHandler(fileWatcher_Error);
    fileWatcher.Created += new FileSystemEventHandler(fileWatcher_Created);
    fileWatcher.Deleted += new FileSystemEventHandler(fileWatcher_Deleted);
    fileWatcher.EnableRaisingEvents = true;
  }
}

// Implement a IDisposable as FileSystemWatcher is disposable
...
// Add events
void fileWatcher_Deleted(object sender, FileSystemEventArgs e)
{ // FIRED ON ANOTHER THREAD
  if (e.Name == LockFileName)
    FireLockFileDisappeared(e);
}

void fileWatcher_Created(object sender, FileSystemEventArgs e)
{ // FIRED ON ANOTHER THREAD
  if (e.Name == LockFileName)
    FireLockFileAppeared(e);
}

void fileWatcher_Renamed(object sender, RenamedEventArgs e)
{ // FIRED ON ANOTHER THREAD
  // If another file is renamed to our target file 
  if (e.Name == LockFileName)
  {
    FireLockFileAppeared(new FileSystemEventArgs(
      WatcherChangeTypes.Created, LockFileDirectory, LockFileName));
  }
  // If our target file is renamed to something else
  else if (e.OldName == LockFileName)
  {
    FireLockFileDisappeared(new FileSystemEventArgs(
      WatcherChangeTypes.Deleted, LockFileDirectory, LockFileName));
  }
}

// Misc. helpers
public string LockFilePath
{
    get { return this.fullTargetFilePath; }
}

public string LockFileDirectory
{
    get { return this.targetDirectory; }
}

Be aware that the class events are fired on another thread so if you want to update the GUI you will have to marshal any data accross. Also be aware that sometimes the events can be fired multiple times for only 1 real event.

August 12, 2011

C++/CLI Using Keyword


#using <XXX::YYY::ZZZ.dll&gt; <= Add a reference to a particular assembly, goes after precompiled header. Make sure to add the path of the referenced assembly to your C# assembly in VS via your assembly's project properties > C/C++ > General > Resolve #using References

using XXX::YYY::ZZZ::StrategyManager; <= Use a specific type (class, enum, ...) from a referenced assembly

using namespace System::Linq; <= Access all types in a namespace from a referenced assembly

August 11, 2011

Virtual Sealed Method in C++/CLI

example:
 virtual System::String^ DoSomething(...) sealed;
What does the sealed do here?
"sealed" on a function means that you can't override that method in a derived type.

Arrays in C++/CLI

Arrays in C++/CLI
array^ attributes =  enumType->GetCustomAttributes(
  System::FlagsAttribute::typeid, false);

Enum type information in C++/CLI

Testing a generic parameter for enum type information
generic <class TValue>
...MyMethod()
{
  // Testing a generic parameter for enum type information
  
  System::Type^ enumType = TValue::typeid;
  // Only enum types supported
  if (!enumType->IsEnum) 
  {
    throw gcnew System::ArgumentException("...");
  }
  // Only 32 bit integer enum types are supported
  if (System::Enum::GetUnderlyingType(enumType) != System::Int32::typeid) 
  { 
    throw gcnew System::ArgumentException("...");
  }
  // [Flags] type enums not supported
  array<System::Object^>^ attributes = enumType->
    GetCustomAttributes(System::FlagsAttribute::typeid, false);
  if (attributes->Length != 0) 
  {
    throw gcnew System::ArgumentException("...");
  }
}

August 9, 2011

Null-coalescing or ?? operator

Always forgetting the name of this operator!

The ?? operator defines the default value to be returned when a nullable type is assigned to a non-nullable type.
// Set defaultValue to Settings.DefaultValue unless it is null 
// in which case set it to string.Empty
string defaultValue = Settings.DefaultValue ?? string.Empty;