.NET Tips & Tricks, examples, articles

Everything worth mentioning in our opinion which raises issues related to .NET (mostly in C#, but hopefully also in VB, XAML and others) lands here.We make every effort to ensure each of the articles contains sample project with full source code and binaries. Most of the included projects requires Visual Studio 2010. Additional components and dependencies (references) are also included in the projects or as a separate attachment if allowed by its license. Enjoy your reading.

Assembly custom attributes helper

From this article you can learn how to define and use simple helper. Example shows generic methods returning list of any type of Attribute from Assembly. AssemblyHelper class can be easily extended with further functionalities as needed.

Implementation and examples are available both in C# and VB.Net languages.

Implementation

Below you can find implementation we use in Karmian Framework as Assembly helper. All members in AssemblyHelper class should be marked as static, so no instance is needed to invoke any. Class AssemblyHelper itself is marked as sealed as we do not want to allow any inheritance. If you need to add new functionality that supports the Assembly - it should be implemented in the class AssemblyHelper. This approach allows the system to prevent duplication of helpers that are responsible for similar actions.

AssemblyHelper class

using System;
using System.Reflection;
 
namespace Karmian.Framework.Helpers
{
  public sealed class AssemblyHelper
  {
    public static T[] GetCustomAttributes<T>(Assembly assembly) where T : Attribute
    {
      return GetCustomAttributes<T>(assembly, true);
    }
 
    public static T[] GetCustomAttributes<T>(Assembly assembly, bool inherit) where T : Attribute
    {
      if (assembly == null)
        throw new ArgumentNullException("assembly");
      var attributes = assembly.GetCustomAttributes(typeof (T), inherit) as T[];
      return attributes ?? new T[0];
    }
  }
}

Imports System.Reflection
 
Namespace Karmian.Framework.Helpers
  Public NotInheritable Class AssemblyHelper
    Public Shared Function GetCustomAttributes(Of T As Attribute)(assembly As Assembly) As T()
      Return GetCustomAttributes(Of T)(assembly, True)
    End Function
 
    Public Shared Function GetCustomAttributes(Of T As Attribute)(assembly As Assembly, inherit As Boolean) As T()
      If assembly Is Nothing Then
        Throw New ArgumentNullException("assembly")
      End If
      Dim attributes = TryCast(assembly.GetCustomAttributes(GetType(T), inherit), T())
      Return If(attributes, New T(-1) {})
    End Function
  End Class
End Namespace

Usage example

Following code snippet shows how to get GuidAttribute from entry Assembly

var guidAttributes = AssemblyHelper.GetCustomAttributes<GuidAttribute>(Assembly.GetEntryAssembly());
if (guidAttributes.Length > 0)
  MessageBox.Show(guidAttributes[0].Value);

Dim guidAttributes = AssemblyHelper.GetCustomAttributes(Of GuidAttribute)(Assembly.GetEntryAssembly())
If guidAttributes.Length > 0 Then
	MessageBox.Show(guidAttributes(0).Value)
End If

Best Practices for Assembly Loading for portable applications

Some of you have certainly met with many different aspects of developing applications based on extensions. The following publication raises the question of practices for extensions in portable applications.

On msdn pages you will find well designed in theory and with almost identical title as ours: Best Practices for Assembly Loading . In short - we get a knowledge of the 'pros and cons' of different approaches and although unfortunately article lacks of practical examples it is worth to get familiar with it.



Reflections on Default Context Loading

GAC (Global Assembly Cache) as a place for our extensions in the case of architecture design for portable solutions is certainly not appropriate. The contents of the GAC may vary drastically between different operating systems and as a result - does not guarantee the consistency.

A good solution to prevent overfilling base directory is to put extensions in subdirectories. You can very easily define additional search paths (relative to the base directory) for extensions in the configuration file.

<?xml version="1.0"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn: schemas-microsoft-com:asm.v1">
       <!-- you can set more than one probing path -->
      <probing privateBinPath="Plugins;Plugins2\SubPlugins"/>
    </assemblyBinding>
  </runtime>
</configuration>

Application domain search path can also be easily defined in the code by creating a new, additional AppDomain.

static class Program
{
  static void Main()
  {
    AppDomainSetup pluginsDomainSetup = new AppDomainSetup
    {
      ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
      PrivateBinPath = @" Plugins1;Plugins2\SubPlugins"
    };
 
    AppDomain pluginsDomain = AppDomain.CreateDomain
      (Guid.NewGuid().ToString(), AppDomain.CurrentDomain.Evidence, pluginsDomainSetup);
 
    IPlugin plugin = (IPlugin)pluginsDomain.CreateInstanceAndUnwrap
      //this call uses full assembly name which is safe
      ("Plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Plugin.Test");
    //actual use of plugin	
    plugin.Use();		
 
    AppDomain.Unload(pluginsDomain);
  }
}

NotInheritable Class Program
  Private Sub New()
  End Sub
  Private Shared Sub Main()
    Dim pluginsDomainSetup As New AppDomainSetup() With { _
      Key .ApplicationBase = AppDomain.CurrentDomain.BaseDirectory, _
      Key .PrivateBinPath = " Plugins1;Plugins2\SubPlugins" _
    }
 
    Dim pluginsDomain As AppDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), AppDomain.CurrentDomain.Evidence, pluginsDomainSetup)
 
    'this call uses full assembly name which is safe
    Dim plugin As IPlugin = DirectCast(pluginsDomain.CreateInstanceAndUnwrap("Plugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Plugin.Test"), IPlugin)
    'actual use of plugin  
    plugin.Use()
 
    AppDomain.Unload(pluginsDomain)
  End Sub
End Class

Karmian way

As we read in article indicated above:

For assemblies that are loaded without context, the problem can be caused by using the Assembly.LoadFile method to load the same assembly from different paths. The runtime considers two assemblies that are loaded from different paths to be different assemblies, even if their identities are the same.

In addition to type identity problems, multiple versions of an assembly can cause a MissingMethodException if a type that is loaded from one version of the assembly is passed to code that expects that type from a different version. For example, the code might expect a method that was added to the later version.

More subtle errors can occur if the behavior of the type changed between versions. For example, a method might throw an unexpected exception or return an unexpected value.

We do not use multiple application domains and Assembly.Load method as we encountered problems during objects serialization. What we use is Assembly.FileLoad because this way we avoid LoadFromContext exception that is thrown when no valid AppDomain is configured for assembly file path and Assembly.Load method is used. When we load those assemblies we put them in IDictionary collection, where a key is a full name of the loaded assembly. Next, we handle AppDomain.CurrentDomain.AssemblyResolve and if application is looking for a desired assembly, we locate it by its full name so we are sure there is no mistake in PublicKey or assembly version. This pattern gives us also the guarantee that no exact assemblies loaded from different paths are used - our loaded assemblies collection can hold only one assembly under certain key (assembly full name) so the rest is just ignored.

As you can see this pattern is very flexible - code can be easily modified to load extensions from multiple paths (including network sources), dynamic assemblies, streams and other.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
 
namespace Karmian.Core.Providers
{
  [Serializable]
  internal class CorePluginProvider
  {
    public CorePluginProvider()
    {
      Initialize();
    }
 
    public IDictionary<string, Assembly> Plugins { get; private set; }
 
    public void Initialize()
    {
      Plugins = new SortedDictionary<string, Assembly>();
      foreach (var file in Directory.GetFiles(
         AppDomain.CurrentDomain.BaseDirectory + @"Plugins",
         Application.ProductName + ".Plugin.*.dll", 
         SearchOption.TopDirectoryOnly))
      Plugins.Add(AssemblyName.GetAssemblyName(file).FullName, Assembly.LoadFile(file));
 
      AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    }
 
    private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
      if (!Plugins.ContainsKey(args.Name))
        throw new InvalidOperationException(
              String.Format("Could not determine valid assembly in context for assembly name '{0}'.", args.Name));
      return Plugins[args.Name];
    }
  }
}

Imports System.Collections.Generic
Imports System.IO
Imports System.Reflection
Imports System.Windows.Forms
 
Namespace Karmian.Core.Providers
  <Serializable> _
  Friend Class CorePluginProvider
    Public Sub New()
      Initialize()
    End Sub
 
    Public Property Plugins() As IDictionary(Of String, Assembly)
      Get
        Return m_Plugins
      End Get
      Private Set
        m_Plugins = Value
      End Set
    End Property
    Private m_Plugins As IDictionary(Of String, Assembly)
 
    Public Sub Initialize()
      Plugins = New SortedDictionary(Of String, Assembly)()
      For Each file As var In Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory & "Plugins", Application.ProductName & ".Plugin.*.dll", SearchOption.TopDirectoryOnly)
        Plugins.Add(AssemblyName.GetAssemblyName(file).FullName, Assembly.LoadFile(file))
      Next
 
      AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf CurrentDomain_AssemblyResolve
    End Sub
 
    Private Function CurrentDomain_AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
      If Not Plugins.ContainsKey(args.Name) Then
        Throw New InvalidOperationException([String].Format("Could not determine valid assembly in context for assembly name '{0}'.", args.Name))
      End If
      Return Plugins(args.Name)
    End Function
  End Class
End Namespace

Enum description TypeConverter

From this article you can learn how to define and use enum description type converter so sytem will know how to handle enum to string and string to enum values (very helpful in PropertyGrid for example).

Implementation is available both in C# and VB.Net languages.

Implementation

Below you can find implementation we use in Karmian Framework as enum description type converter. It can be easily extended to work also as localizable enum description type converter - Description attribute can be used as a key of localized resource.

EnumDescriptionTypeConverter class

using System;
using System.ComponentModel;
using System.Globalization;
 
namespace Karmian.Core.TypeConverters
{
  public class EnumDescriptionTypeConverter : EnumConverter
  {
    public EnumDescriptionTypeConverter(Type type) : base(type)
    {
    }
 
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
      return sourceType == typeof(string) || TypeDescriptor.GetConverter(typeof(Enum)).CanConvertFrom(context, sourceType);
    }
 
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
      if (value is string)
        return GetEnumValue(EnumType, (string) value);
      if (value is Enum)
        return GetEnumDescription((Enum) value);
      return base.ConvertFrom(context, culture, value);
    }
 
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
      return value is Enum && destinationType == typeof(string)
          ? GetEnumDescription((Enum)value)
          : (value is string && destinationType == typeof(string)
            ? GetEnumDescription(EnumType, (string)value)
            : base.ConvertTo(context, culture, value, destinationType));
    }
 
    public static string GetEnumDescription(Enum value)
    {
      var fieldInfo = value.GetType().GetField(value.ToString());
      var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
      return (attributes.Length > 0) ? attributes[0].Description : value.ToString();
    }
 
    public static string GetEnumDescription(Type value, string name)
    {
      var fieldInfo = value.GetField(name);
      var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
      return (attributes.Length > 0) ? attributes[0].Description : name;
    }
 
    public static object GetEnumValue(Type value, string description)
    {
      var fields = value.GetFields();
      foreach (var fieldInfo in fields)
      {
        var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attributes.Length > 0 && attributes[0].Description == description)
          return fieldInfo.GetValue(fieldInfo.Name);
        if (fieldInfo.Name == description)
          return fieldInfo.GetValue(fieldInfo.Name);
      }
      return description;
    }
  }
}

Imports System.ComponentModel
Imports System.Globalization
 
Namespace Karmian.Core.TypeConverters
  Public Class EnumDescriptionTypeConverter
    Inherits EnumConverter
    Public Sub New(type As Type)
      MyBase.New(type)
    End Sub
 
    Public Overrides Function CanConvertFrom(context As ITypeDescriptorContext, sourceType As Type) As Boolean
      Return sourceType Is GetType(String) OrElse TypeDescriptor.GetConverter(GetType([Enum])).CanConvertFrom(context, sourceType)
    End Function
 
    Public Overrides Function ConvertFrom(context As ITypeDescriptorContext, culture As CultureInfo, value As Object) As Object
      If TypeOf value Is String Then
        Return GetEnumValue(EnumType, DirectCast(value, String))
      End If
      If TypeOf value Is [Enum] Then
        Return GetEnumDescription(DirectCast(value, [Enum]))
      End If
      Return MyBase.ConvertFrom(context, culture, value)
    End Function
 
    Public Overrides Function ConvertTo(context As ITypeDescriptorContext, culture As CultureInfo, value As Object, destinationType As Type) As Object
      Return If(TypeOf value Is [Enum] AndAlso destinationType Is GetType(String), GetEnumDescription(DirectCast(value, [Enum])), (If(TypeOf value Is String AndAlso destinationType Is GetType(String), GetEnumDescription(EnumType, DirectCast(value, String)), MyBase.ConvertTo(context, culture, value, destinationType))))
    End Function
 
    Public Shared Function GetEnumDescription(value As [Enum]) As String
      Dim fieldInfo = value.[GetType]().GetField(value.ToString())
      Dim attributes = DirectCast(fieldInfo.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
      Return If((attributes.Length > 0), attributes(0).Description, value.ToString())
    End Function
 
    Public Shared Function GetEnumDescription(value As Type, name As String) As String
      Dim fieldInfo = value.GetField(name)
      Dim attributes = DirectCast(fieldInfo.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
      Return If((attributes.Length > 0), attributes(0).Description, name)
    End Function
 
    Public Shared Function GetEnumValue(value As Type, description As String) As Object
      Dim fields = value.GetFields()
      For Each fieldInfo As FieldInfo In fields
        Dim attributes = DirectCast(fieldInfo.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
        If attributes.Length > 0 AndAlso attributes(0).Description = description Then
          Return fieldInfo.GetValue(fieldInfo.Name)
        End If
        If fieldInfo.Name = description Then
          Return fieldInfo.GetValue(fieldInfo.Name)
        End If
      Next
      Return description
    End Function
  End Class
End Namespace

Usage example

EnumDescriptionTypeConverter class is used here as default type converter

using System;
using System.ComponentModel;
using Karmian.Core.TypeConverters;
 
namespace GoldenSharp.Common
{
  [TypeConverter(typeof(EnumDescriptionTypeConverter))]
  public enum TargetFramework
  {
    [Description("Default")]
    Default,
    [Description("ISO-1")]
    ISO_1,
    [Description("ISO-2")]
    ISO_2,
    [Description("C# 3.0")]
    CS_3
  }
}

Imports System.ComponentModel
Imports Karmian.Core.TypeConverters
 
Namespace GoldenSharp.Common
	<TypeConverter(GetType(EnumDescriptionTypeConverter))> _
	Public Enum TargetFramework
		<Description("Default")> _
		[Default]
		<Description("ISO-1")> _
		ISO_1
		<Description("ISO-2")> _
		ISO_2
		<Description("C# 3.0")> _
		CS_3
	End Enum
End Namespace

How to resize description area in PropertyGrid


From this article you can learn how to resize description area in PropertyGrid. Example shows use of reflection in order to dig to private members of PropertyGrid - not accessible in normal way.

Implementation is available both in C# and VB.Net languages. Use buttons below to switch language visibility.

Implementation

System.Reflection namespace needs to be put in usings.

private static void ChangeDescriptionHeight(PropertyGrid grid, int height)
{
  if (grid == null) throw new ArgumentNullException("grid");
 
  foreach (Control control in grid.Controls)
   if (control.GetType().Name == "DocComment")
    {
      FieldInfo fieldInfo = control.GetType().BaseType.GetField("userSized",
        BindingFlags.Instance |
        BindingFlags.NonPublic);
      fieldInfo.SetValue(control, true);
      control.Height = height;
      return;
    }
}

Private Shared Sub ChangeDescriptionHeight(grid As PropertyGrid, height As Integer)
  If grid Is Nothing Then
    Throw New ArgumentNullException("grid")
  End If
 
  For Each control As Control In grid.Controls
    If control.[GetType]().Name = "DocComment" Then
      Dim fieldInfo As FieldInfo = control.[GetType]().BaseType.GetField("userSized", 
	    BindingFlags.Instance Or BindingFlags.NonPublic)
      fieldInfo.SetValue(control, True)
      control.Height = height
      Return
      End If
  Next
End Sub

How to sanitize a file name using LINQ

From this article you can learn how to sanitize a file name using simple LINQ query. SanitizeFilename function takes two parameters: Implementation is available both in C# and VB.Net languages. Use buttons below to switch language visibility.

Implementation

System.Linq namespace needs to be put in usings.

public static string SanitizeFileName(string name, char replaceChar)
{
  return Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, replaceChar));
}
Public Shared Function SanitizeFileName(name As String, replaceChar As Char) As String
  Return Path.GetInvalidFileNameChars().Aggregate(name, Function(current, c) current.Replace(c, replaceChar))
End Function

LINQ lambda expressions in universal generic equality comparer


Implementation

Below you can find implementation we use as universal equality comparer. Only lambda expressions as in LINQ queries are needed to measure items. Class LambdaComparer is generic, so you can use it on any type you want to.

LambdaComparer class

using System;
using System.Collections.Generic;
 
namespace Karmian.Framework.Helpers
{
    public class LambdaComparer<T> : IEqualityComparer<T>
    {
        public LambdaComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
        {
            _equals = equals;
            _getHashCode = getHashCode;
        }
 
        readonly Func<T, T, bool> _equals;
        public bool Equals(T x, T y)
        {
            return _equals(x, y);
        }
 
        readonly Func<T, int> _getHashCode;
        public int GetHashCode(T obj)
        {
            return _getHashCode(obj);
        }
    } 
}
Imports System.Collections.Generic
 
Namespace Karmian.Framework.Helpers
  Public Class LambdaComparer(Of T)
    Implements IEqualityComparer(Of T)
    Public Sub New(equals As Func(Of T, T, Boolean), getHashCode As Func(Of T, Integer))
      _equals = equals
      _getHashCode = getHashCode
    End Sub
 
    ReadOnly _equals As Func(Of T, T, Boolean)
    Public Overloads Function Equals(x As T, y As T) As Boolean Implements IEqualityComparer(Of T).Equals
      Return _equals(x, y)
    End Function
 
    ReadOnly _getHashCode As Func(Of T, Integer)
    Public Overloads Function GetHashCode(obj As T) As Integer Implements IEqualityComparer(Of T).GetHashCode
      Return _getHashCode(obj)
    End Function
  End Class
End Namespace

Usage example

City class used as comparison item

public class City
{
    public string Name { get; set; }
    public int Population { get; set; }
 
    public override string ToString()
    {
        return string.Format("\tName: {0}\t, Population: {1}", Name, Population);
    }
}
Public Class City
  Public Property Name() As String
    Get
      Return m_Name
    End Get
    Set
      m_Name = Value
    End Set
  End Property
  Private m_Name As String
  Public Property Population() As Integer
    Get
      Return m_Population
    End Get
    Set
      m_Population = Value
    End Set
  End Property
  Private m_Population As Integer
 
  Public Overrides Function ToString() As String
    Return String.Format(vbTab & "Name: {0}" & vbTab & ", Population: {1}", Name, Population)
  End Function
End Class

Application showing typical use

class Program
{
    private static void Main()
    {
        var cities = CreateCities();
        Console.WriteLine("Normal listing:");
        foreach (var city in cities)
            Console.WriteLine(city);
        Console.WriteLine("Press any key to continue...\n");
        Console.ReadKey(true);
 
        Console.WriteLine("Distinct by name:");
        foreach (var city in cities.Distinct(
            new LambdaComparer<City>(
                (c1, c2) => c1.Name.Equals(c2.Name),
                c => 1 /* force compare other than by instance */)))
            Console.WriteLine(city);
        Console.WriteLine("Press any key to continue...\n");
        Console.ReadKey(true);
 
        Console.WriteLine("More than one property is doubled:");
        foreach (var city in cities.Distinct(
            new LambdaComparer<City>(
                (c1, c2) => c1.Population.Equals(c2.Population) || c2.Name.Equals(c2.Name),
                c => 1 /* force compare other than by instance */)))
            Console.WriteLine(city);
        Console.WriteLine("Press any key to continue...\n");
        Console.ReadKey(true);
 
        Console.WriteLine("Contains city with population of 789? :");
        Console.WriteLine(cities.Contains(
            new City { Name = "City789", Population = 789 },
            new LambdaComparer<City>(
                (c1, c2) => c1.Population.Equals(c2.Population),
                c => c.GetHashCode() /* instance has nothing to do here */)));
        Console.WriteLine("Press any key to continue...\n");
        Console.ReadKey(true);
 
        Console.WriteLine("HashSet with equality set to City.Name property? :");
        var hashSet = new HashSet<City>(
            new LambdaComparer<City>(
                (c1, c2) => c1.Name.Equals(c2.Name),
                c => 1 /* force compare other than by instance */))
			        {
			            new City {Name = "aaa"},
			            new City {Name = "aba"},
			            new City {Name = "aaa"},
			            new City {Name = "baa"},
			            new City {Name = "aba"}
			        };
        foreach (var city in hashSet)
            Console.WriteLine(city);
        Console.WriteLine("Press any key to exit...\n");
        Console.ReadKey(true);
    }
 
    private static IEnumerable<City> CreateCities()
    {
        return new List<City>
			{
			    new City { Name = "City1", Population = 123 },
			    new City { Name = "City2", Population = 456 },
			    new City { Name = "City1", Population = 789 },
			    new City { Name = "City3", Population = 123 },
			    new City { Name = "City4", Population = 987 },
			    new City { Name = "City4", Population = 654 }
			};
    }
}
Class Program
  Private Shared Sub Main()
    Dim cities = CreateCities()
    Console.WriteLine("Normal listing:")
    For Each city As var In cities
      Console.WriteLine(city)
    Next
    Console.WriteLine("Press any key to continue..." & vbLf)
    Console.ReadKey(True)
 
    Console.WriteLine("Distinct by name:")
      ' force compare other than by instance 
    For Each city As var In cities.Distinct(New LambdaComparer(Of City)(Function(c1, c2) c1.Name.Equals(c2.Name), Function(c) 1))
      Console.WriteLine(city)
    Next
    Console.WriteLine("Press any key to continue..." & vbLf)
    Console.ReadKey(True)
 
    Console.WriteLine("More than one property is doubled:")
      ' force compare other than by instance 
    For Each city As var In cities.Distinct(New LambdaComparer(Of City)(Function(c1, c2) c1.Population.Equals(c2.Population) OrElse c2.Name.Equals(c2.Name), Function(c) 1))
      Console.WriteLine(city)
    Next
    Console.WriteLine("Press any key to continue..." & vbLf)
    Console.ReadKey(True)
 
    Console.WriteLine("Contains city with population of 789? :")
      ' instance has nothing to do here 
    Console.WriteLine(cities.Contains(New City() With { _
      Key .Name = "City789", _
      Key .Population = 789 _
    }, New LambdaComparer(Of City)(Function(c1, c2) c1.Population.Equals(c2.Population), Function(c) c.GetHashCode())))
    Console.WriteLine("Press any key to continue..." & vbLf)
    Console.ReadKey(True)
 
    Console.WriteLine("HashSet with equality set to City.Name property? :")
      ' force compare other than by instance 
    Dim hashSet = New HashSet(Of City)(New LambdaComparer(Of City)(Function(c1, c2) c1.Name.Equals(c2.Name), Function(c) 1)) From { _
      New City() With { _
        Key .Name = "aaa" _
      }, _
      New City() With { _
        Key .Name = "aba" _
      }, _
      New City() With { _
        Key .Name = "aaa" _
      }, _
      New City() With { _
        Key .Name = "baa" _
      }, _
      New City() With { _
        Key .Name = "aba" _
      } _
    }
    For Each city As var In hashSet
      Console.WriteLine(city)
    Next
    Console.WriteLine("Press any key to exit..." & vbLf)
    Console.ReadKey(True)
  End Sub
 
  Private Shared Function CreateCities() As IEnumerable(Of City)
    Return New List(Of City)() From { _
      New City() With { _
        Key .Name = "City1", _
        Key .Population = 123 _
      }, _
      New City() With { _
        Key .Name = "City2", _
        Key .Population = 456 _
      }, _
      New City() With { _
        Key .Name = "City1", _
        Key .Population = 789 _
      }, _
      New City() With { _
        Key .Name = "City3", _
        Key .Population = 123 _
      }, _
      New City() With { _
        Key .Name = "City4", _
        Key .Population = 987 _
      }, _
      New City() With { _
        Key .Name = "City4", _
        Key .Population = 654 _
      } _
    }
  End Function
End Class

Output

Normal listing:
        Name: City1     , Population: 123
        Name: City2     , Population: 456
        Name: City1     , Population: 789
        Name: City3     , Population: 123
        Name: City4     , Population: 987
        Name: City4     , Population: 654
Press any key to continue...

Distinct by name:
        Name: City1     , Population: 123
        Name: City2     , Population: 456
        Name: City3     , Population: 123
        Name: City4     , Population: 987
Press any key to continue...

More than one property is duplicated:
        Name: City1     , Population: 123
Press any key to continue...

Contains city with population of 789? :
True
Press any key to continue...

HashSet with equality set to City.Name property :
        Name: aaa       , Population: 0
        Name: aba       , Population: 0
        Name: baa       , Population: 0
Press any key to exit...
AttachmentSize
[binaries] KarmianEqualityComparer.zip41.67 KB
[sources] KarmianEqualityComparer.zip46.45 KB

Running executable from MemoryStream / Resources / binary array




Be aware, that executables or assemblies loaded in such manner share main application (host) domain. This implies that if there is any reference needed other from GAC (GlobalAssemblyCache) or host working directory - you will need to handle AppDomain.CurrentDomain.AssemblyResolve event or create new AppDomain for example. Sample code:
using System;
using System.IO;
using System.Reflection;
 
namespace ExecutableMemoryStream
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                if (args.Length == 0)
                    throw new TargetParameterCountException(
                             "Expected at least one parameter containing executable path.");
 
                using (FileStream fileStream = new FileStream(args[0], FileMode.Open))
                using (BinaryReader reader = new BinaryReader(fileStream))
                {
                    byte[] bin = reader.ReadBytes(Convert.ToInt32(fileStream.Length));
                    Assembly assembly = Assembly.Load(bin);
                    MethodInfo method = assembly.EntryPoint;
                    if (method != null)
                    {
                        object o = assembly.CreateInstance(method.ReflectedType.Name);
                        if (method.GetParameters().Length == 0)
                            method.Invoke(o, new object[0]);
                        else
                        {
                            string[] parameters = new string[args.Length - 1];
                            for (int i = 1; i < args.Length; i++)
                                parameters[i - 1] = args[i];
                            method.Invoke(o, new[] { parameters });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.BackgroundColor = ConsoleColor.Red;
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine();
                Console.WriteLine(ex.Message.PadRight(80));
                Console.BackgroundColor = ConsoleColor.Black;
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("Hit 'd' for details. Any other key will terminate application.");
                if (Console.ReadKey(true).KeyChar == 'd')
                {
                    Console.ForegroundColor = ConsoleColor.Gray;
                    Console.WriteLine();
                    Console.WriteLine(ex.StackTrace);
                    Console.ReadKey();
                }
            }
        }
    }
}
Imports System.IO
Imports System.Reflection
 
Namespace ExecutableMemoryStream
  Class Program
    Private Shared Sub Main(args As String())
      Try
        If args.Length = 0 Then
          Throw New TargetParameterCountException("Expected at least one parameter containing executable path.")
        End If
 
        Using fileStream As New FileStream(args(0), FileMode.Open)
          Using reader As New BinaryReader(fileStream)
            Dim bin As Byte() = reader.ReadBytes(Convert.ToInt32(fileStream.Length))
            Dim assembly__1 As Assembly = Assembly.Load(bin)
            Dim method As MethodInfo = assembly__1.EntryPoint
            If method IsNot Nothing Then
              Dim o As Object = assembly__1.CreateInstance(method.ReflectedType.Name)
              If method.GetParameters().Length = 0 Then
                method.Invoke(o, New Object(-1) {})
              Else
                Dim parameters As String() = New String(args.Length - 2) {}
                For i As Integer = 1 To args.Length - 1
                  parameters(i - 1) = args(i)
                Next
                method.Invoke(o, New () {parameters})
              End If
            End If
          End Using
        End Using
      Catch ex As Exception
        Console.BackgroundColor = ConsoleColor.Red
        Console.ForegroundColor = ConsoleColor.White
        Console.WriteLine()
        Console.WriteLine(ex.Message.PadRight(80))
        Console.BackgroundColor = ConsoleColor.Black
        Console.ForegroundColor = ConsoleColor.Yellow
        Console.WriteLine("Hit 'd' for details. Any other key will terminate application.")
        If Console.ReadKey(True).KeyChar = "d"C Then
          Console.ForegroundColor = ConsoleColor.Gray
          Console.WriteLine()
          Console.WriteLine(ex.StackTrace)
          Console.ReadKey()
        End If
      End Try
    End Sub
  End Class
End Namespace
Below you can find full sources with two dummy applications. Drag & drop .NET executables only (managed) onto ExecutableMemoryStream.exe to load & invoke from memory stream.
AttachmentSize
[binaries] ExecutableMemoryStream.zip29.29 KB
[sources] ExecutableMemoryStream.zip42.86 KB