I have tried to enter non ASCII characters in C# identifiers, and the program compiles and runs just fine (at least at first glance). To be more precise, I use Croatian diacritical characters (čćđšž) in enums. I really need to use these special characters because I map string enums as value objects in NHibernate. It would be really ugly to have to avoid these standard characters when showing them as lookups to the user. Before I start using enums this way on a big scale, I really need to know if there are any implications (hidden pitfalls) to such a programming technique (especially in regard to NHibernate开发者_Python百科)? Or, if you have a better way handling this, please let me know.
Also, are there any problems with tools used for refactoring, SVN etc.
The C# language uses unicode encoding, which means your special character will not be a problem. However, I like to keep my code in english without special characters. I am yet to find a problem which justifies using culture specific naming.
From the C# language specification:
A C# program consists of one or more source files, known formally as compilation units (§9.1). A source file is an ordered sequence of Unicode characters. Source files typically have a one-to-one correspondence with files in a file system, but this correspondence is not required. For maximal portability, it is recommended that files in a file system be encoded with the UTF-8 encoding.
Section 2.1 (Microsoft C# Language Specification)
I made this custom user type to convert safely from enum to int for NHibernate.You should change the function to use string instead of int. Then you could change the method "NullSafeGet" to make the conversion from the database value to your enum value. Making so, you could use normal characters for your enum values and user readable names in your database.
EDIT : I made the changes myself. You can use this class for your custom user type. You should implement the "SpecialConversion" method to convert from your special string to enum and vice-versa.
public class EnumToSpecialStringType<TEnum> : IUserType
{
#region IUserType Members
/// <summary>
/// Reconstruct an object from the cacheable representation. At the very least this
/// method should perform a deep copy if the type is mutable. (optional operation)
/// </summary>
/// <param name="cached">the object to be cached</param>
/// <param name="owner">the owner of the cached object</param>
/// <returns>a reconstructed object from the cachable representation</returns>
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
/// <summary>
/// Return a deep copy of the persistent state, stopping at entities and at collections.
/// </summary>
/// <param name="value">generally a collection element or entity field</param>
/// <returns>a copy</returns>
public object DeepCopy(object value)
{
return value;
}
/// <summary>
/// Transform the object into its cacheable representation. At the very least this
/// method should perform a deep copy if the type is mutable. That may not be enough
/// for some implementations, however; for example, associations must be cached as
/// identifier values. (optional operation)
/// </summary>
/// <param name="value">the object to be cached</param>
/// <returns>a cacheable representation of the object</returns>
public object Disassemble(object value)
{
return DeepCopy(value);
}
/// <summary>
/// Compare two instances of the class mapped by this type for persistent "equality"
/// ie. equality of persistent state
/// </summary>
/// <param name="x"/>
/// <param name="y"/>
/// <returns/>
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Equals(y);
}
/// <summary>
/// Get a hashcode for the instance, consistent with persistence "equality"
/// </summary>
public int GetHashCode(object x)
{
return x.GetHashCode();
}
/// <summary>
/// Are objects of this type mutable?
/// </summary>
public bool IsMutable
{
get { return false; }
}
/// <summary>
/// Retrieve an instance of the mapped class from a ADO.NET resultset.
/// Implementors should handle possibility of null values.
/// </summary>
/// <param name="rs">a IDataReader</param>
/// <param name="names">column names</param>
/// <param name="owner">the containing entity</param>
/// <returns/>
/// <exception cref="HibernateException">HibernateException</exception>
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var value = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
// Put you special conversion here (string -> enum)
var converted = SpecialConversion(value);
return converted;
}
/// <summary>
/// Write an instance of the mapped class to a prepared statement.
/// Implementors should handle possibility of null values.
/// A multi-column type should be written to parameters starting from index.
/// </summary>
/// <param name="cmd">a IDbCommand</param>
/// <param name="value">the object to write</param>
/// <param name="index">command parameter index</param>
/// <exception cref="HibernateException">HibernateException</exception>
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var parameter = (IDataParameter)cmd.Parameters[index];
// Do conversion from your enum to database string value
var converted = SpecialConversion(value);
parameter.Value = converted;
}
/// <summary>
/// During merge, replace the existing (<paramref name="target"/>) value in the entity
/// we are merging to with a new (<paramref name="original"/>) value from the detached
/// entity we are merging. For immutable objects, or null values, it is safe to simply
/// return the first parameter. For mutable objects, it is safe to return a copy of the
/// first parameter. For objects with component values, it might make sense to
/// recursively replace component values.
/// </summary>
/// <param name="original">the value from the detached entity being merged</param>
/// <param name="target">the value in the managed entity</param>
/// <param name="owner">the managed entity</param>
/// <returns>the value to be merged</returns>
public object Replace(object original, object target, object owner)
{
return original;
}
/// <summary>
/// The type returned by <c>NullSafeGet()</c>
/// </summary>
public Type ReturnedType
{
get
{
return typeof(TEnum);
}
}
/// <summary>
/// The SQL types for the columns mapped by this type.
/// </summary>
public SqlType[] SqlTypes
{
get
{
return new[] { new SqlType(DbType.String) };
}
}
#endregion
}
I use fluent NHibernate and my way to map my column with this custom usertype was:
Map(x => x.PropertyName, "ColumnName").CustomType<EnumToSpecialStringType<EnumType>>();
EDIT: Here's a post about how to map a user type using a xml mapping file:
nHibernate mapping to custom types
I'm just writing examples here to show how you can do the mapping.
Consider your enum where all values contains non-ASCII characters:
public enum MyEnum
{
NonAsciiName1,
NonAsciiName2,
NonAsciiName3,
}
You could change it to do this where all values have ASCII characters:
public enum MyEnum
{
AsciiName1,
AsciiName2,
AsciiName3,
}
public static class MyEnumExtensions
{
static readonly Dictionary<MyEnum, string> map = new Dictionary<MyEnum, string>
{
{ MyEnum.AsciiName1, "NonAsciiName1" },
{ MyEnum.AsciiName2, "NonAsciiName2" },
{ MyEnum.AsciiName3, "NonAsciiName3" },
};
public static string GetValue(this MyEnum key)
{
return map[key];
}
}
Then your code would need small changes to use this:
// you probably had something like:
var myEnumValue = MyEnum.NonAsciiName1;
var value = myEnumValue.ToString();
// now becomes:
var myEnumValue = MyEnum.AsciiName1;
var value = myEnumValue.GetValue();
Or has DEHAAS suggested, use attributes which works in a similar way. But then you'd have to use reflection to get the values which has a bit of a performance penalty.
using System.ComponentModel;
public enum MyEnum
{
[DescriptionAttribute("NonAsciiName1")] AsciiName1,
[DescriptionAttribute("NonAsciiName2")] AsciiName2,
[DescriptionAttribute("NonAsciiName3")] AsciiName3,
}
public static class MyEnumExtensions
{
public static string GetValue(this MyEnum key)
{
return typeof(MyEnum).GetField(key.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.Single()
.Description;
}
}
Yes, I consider that a pitfall.
Showing lookups to the user and keeping a list of the possible (unique) items in the database can better be kept separately. Especially if your application is to support other languages in the future.
Not very object oriented you could have a class in your code that is a direct mapping between your enum and the value of the respective language, like in a nhibernate mapping like this:
<class name="DropDown" table="DropDown_Language">
<!-- map onto the ENUMeration -->
<id name="UniqueID" column="Id" type="Int32" >
<generator class="identity" />
</id>
<property name="DropDownType" column="DropDownTypeId" type="DropDownType, Domain"/>
<property name="Language" column="LanguageId" type="LanguageReferenceType, "/>
<property name="DropDownTypeDescription" column="DropDownTypeDescription" type="string"/>
</class>
In the DropDownTypeDescription column you would then put the values of the dropdown.
Hope I understood your question :-)
精彩评论