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)? 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 :-)
Related
I'm trying to create a documentation response header in a method like this
public Task<CustomObject> AuthUserAsync()
{
}
So, as I understand it should be something like
/// <summary>
/// Description.
/// </summary>
/// <returns>A <see cref="Task"/><<see cref="CustomObject"/>> representing the operation.</returns>
public Task<CustomObject> AuthUserAsync()
{
}
But I'm unable to see the documentation when hover the mouse on the method
Any ideas?
The proper syntax to use a template argument in a documentation header is:
/// <summary>
/// Description.
/// </summary>
/// <returns>A <see cref="Task{TResult}"/> representing the operation.</returns>
public Task<CustomObject> AuthUserAsync()
{
}
Note that the angle brackets are replaced by {}, because angle brackets would make the XML invalid at that point. You cannot put Task{int} there, even though that would be more exact, because Task<int> is not a type you can create a reference to. If you write it without the cref, you can do
/// <returns>A Task>int< representing the operation.</returns>
You are still not allowed to use literal < or > characters, though.
At the moment I'm working on funcionality that involves exporting and importing data to Xlsx file. Here's what I want to do: I want to have an attribute I can put above a property like this.
public class MyClass
{
[XlsxColumn("Column 1")]
public string myProperty1 { get; set; }
public int myProperty2 { get; set; }
}
So far I don't have problems, but then I want to "store references" to properties marked with the XlsxColumn attribute. I'm using reflection
to store properties data in List
var propsList = MyClass.GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(XlsxColumn)));
I have a list with all properties marked with XlsxColumn (only myProperty1 in this example).
EDIT: The problem is I don't know how to loop over properties in MyClass, but only properties with XlsxColumn attribute (so all PropertyInfo objects stored in propsList variable), without resorting to reflection with each object saved to Xlsx file.
I'm restricted to .NET 4.0.
Thanks for your time.
MyClass.GetProperties() does not work because you have to get the type of the class to invoke the GetProperties method. Otherwise you are invoking a static method called GetProperties defined in the MyClass class.
var propsList = typeof(MyClass).GetProperties().Where(
prop => prop.IsDefined(typeof(XlsxColumnAttribute), false)).ToList();
If you just want the names (IList<string>):
var propsList = typeof(Excel).GetProperties().Where(
prop => prop.IsDefined(typeof(XlsxColumnAttribute), false))
.Select(prop=> prop.Name)
.ToList();
to use .Where you have to include System.Linq
I must say that I am not sure if this is the solution you are looking for. Because I could not quite make out what your question is. Well I have tried to provide an answer as much as I could figure out.
I went for a static class for CachingPropetyProvider but you can go for an instance class and use a dependency injection library and use it as a Singleton too. Moreover, I have written extensive comments so It is as self explanatory as possible.
Let us define MyClass. I also deliberately changed it a little bit.
public class MyClass
{
[XlsxColumn("Column 1")]
public string MyProperty1 { get; set; }
[XlsxColumn("Column 2")]
public int MyProperty2 { get; set; }
}
I also defined a MetaInfo class to hold the cached information.
public class MetaInfo {
/// <summary>
/// Immutable class for holding PropertyInfo and XlsxColumn info.
/// </summary>
/// <param name="info">PropertyInfo</param>
/// <param name="attr">XlsxColumn</param>
public MetaInfo(PropertyInfo info, XlsxColumn attr) {
PropertyInfo = info;
Attribute = attr;
}
/// <summary>
/// PropertyInfo. You may want to access the value inside the property.
/// </summary>
public PropertyInfo PropertyInfo { get; }
/// <summary>
/// Attribute. You may want to access information hold inside the attribute.
/// </summary>
public XlsxColumn Attribute { get; }
}
And lastly the main guy. This guy is responsible for providing all the data about classes
public class CachingPropProvider {
/// <summary>
/// Holds the meta information for each type.
/// </summary>
private static readonly ConcurrentDictionary<Type, List<MetaInfo>> TypeCache;
/// <summary>
/// Static constructor is guaranteed to run only once.
/// </summary>
static CachingPropProvider() {
//Initialize the cache.
TypeCache = new ConcurrentDictionary<Type, List<MetaInfo>>();
}
/// <summary>
/// Gets the MetaInfo for the given type. Since We use ConcurrentDictionary it is thread safe.
/// </summary>
/// <typeparam name="T">Type parameter</typeparam>
public static IEnumerable<MetaInfo> GetCachedStuff<T>() {
//If Type exists in the TypeCache, return the cached value
return TypeCache.GetOrAdd(typeof(T),Factory);
}
/// <summary>
/// Factory method to use to extract MetaInfo when Cache is not hit.
/// </summary>
/// <param name="type">Type to extract info from</param>
/// <returns>A list of MetaInfo. An empty List, if no property has XlsxColumn attrbiute</returns>
private static List<MetaInfo> Factory(Type #type) {
//If Type does not exist in the TypeCahce runs Extractor
//Method to extract metainfo for the given type
return #type.GetProperties().Aggregate(new List<MetaInfo>(), Extractor);
}
/// <summary>
/// Extracts MetaInfo from the given property info then saves it into the list.
/// </summary>
/// <param name="seedList">List to save metainfo into</param>
/// <param name="propertyInfo">PropertyInfo to try to extract info from</param>
/// <returns>List of MetaInfo</returns>
private static List<MetaInfo> Extractor(List<MetaInfo> seedList,PropertyInfo propertyInfo) {
//Gets Attribute
var customattribute = propertyInfo.GetCustomAttribute<XlsxColumn>();
//If custom attribute is not null, it means it is defined
if (customattribute != null)
{
//Extract then add it into seed list
seedList.Add(new MetaInfo(propertyInfo, customattribute));
}
//Return :)
return seedList;
}
}
Finally let us see how to use the solution. It is pretty straightforward actually.
//Has 2 values inside
var info = CachingPropProvider.GetCachedStuff<MyClass>();
Assume, i have a method which parameters are classes with already defined documentation:
/// <summary>
/// Get criterion from table.
/// </summary>
/// <param name="plySide"></param>
/// <param name="criterionType"></param>
/// <returns></returns>
public Criterion GetCriterion(PlySide plySide, CriterionType criterionType)
{
// some code
}
PlySide class has it's own xml-documentation:
/// <summary>
/// Sides of monoply.
/// </summary>
public enum PlySide
{
// some code
}
As you can see in GetCriterion method i didn't define any doc for the plySide param tag.
My question is should i duplicate description of a parameter or should i remove param tags?
The PlySide documentation will describe what the type is.
The plySide documentation should describe what role that parameter plays within the GetCriterion method.
Those will usually be subtly (or not so subtly) different.
You shouldn't duplicate it but give a context aware description of the parameter. In your case it could be something like this (perhaps it's toally wrong, but I don't know the implementation and intention of your method):
/// <summary>
/// Get criterion from table.
/// </summary>
/// <param name="plySide">Monopoly side to get criterion for</param>
/// <param name="criterionType">Criterion to get for the given monopoly side</param>
/// <returns></returns>
public Criterion GetCriterion(PlySide plySide, CriterionType criterionType)
{
// some code
}
I'm writing a COM DLL in C# to handle the import and export of X.12 format documents, so I would be able to use it in an Access database and a custom program for handling EDI with my company. I've gotten a DLL to compile but with disappointing results, and I'm wondering if I'm missing something; COM "from scratch" is new ground to me (I've made a ribbon for Excel before but a wizard handled all of that).
I've read this article on MSDN and came across this question here to get my DLL and TLB to compile and register. This is the skeleton of my X12Segment class and the interface for COM visibility:
using System;
using System.Collections;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace X12
{
[Guid("28A76274-05EE-45B2-A8EF-ADD5A5B351DE"),
ComVisible(true)]
public interface IX12Segment
{
[DispId(1)]
string SegmentType { get; set; }
[DispId(2)]
string[] Fields { get; set; }
[DispId(3)]
char FieldDelimiter { get; set; }
[DispId(4)]
char SegmentDelimiter { get; set; }
[DispId(5)]
string ToString(char sep, char eol);
[DispId(6)]
string ToString();
[DispId(7)]
Type GetFieldEnum();
}
[Guid("B321599A-E5EC-4510-A021-E9A8B4D6293E"),
ClassInterface(ClassInterfaceType.None),
ComVisible(true)]
public class X12Segment : IX12Segment
{
private string _type;
protected ArrayList _fields;
protected short _minFields = -1;
protected short _maxFields = -1;
protected short[][] _fieldSizes = { new short[] { -1, -1 } };
protected char _sep;
protected char _eol;
public enum Field { }
/// <summary>
/// Creates a new X.12 segment of the supplied type,
/// optionally with a supplied number of fields or
/// values.
/// </summary>
/// <param name="segType">The type of segment (eg "ISA", "GS")</param>
/// <param name="fields">Each string is a field
/// within the segment</param>
public X12Segment(string segType, params string[] fields)
: this(segType)
{
//Do cool stuff
}
/// <summary>
/// Creates a new X.12 segment from a string.
/// </summary>
/// <param name="segType">The type of segment (eg "ISA", "GS")</param>
/// <param name="segment">The string to parse</param>
public X12Segment(string segType, string segment)
: this(segType)
{
//Do cool stuff
}
/// <summary>
/// Creates a new X.12 segment of the supplied type,
/// optionally with a supplied number of fields or
/// values.
/// </summary>
/// <param name="segType">The type of segment (eg "ISA", "GS")</param>
/// <param name="fieldCount">The number of fields
/// in this segment</param>
public X12Segment(string segType, int fieldCount) : this(segType)
{
//Do cool stuff
}
/// <summary>
/// Creates a new X.12 segment of the supplied type,
/// optionally with a supplied number of fields or
/// values.
/// </summary>
/// <param name="segType">The type of segment (eg "ISA", "GS")</param>
public X12Segment(string segType) : this()
{
//Do cool stuff
}
public X12Segment()
{
//Do cool stuff
}
/// <summary>
/// Gets or sets the segment type.
/// </summary>
public string SegmentType
{
get;
set;
}
/// <summary>
/// Gets or sets all of the fields in the segment,
/// in the form of an array of strings.
/// </summary>
public string[] Fields
{
get;
set;
}
/// <summary>
/// Gets or sets the character used to seperate fields in the segment.
/// </summary>
public char FieldDelimiter
{
get;
set;
}
/// <summary>
/// Gets or sets the character denoting the end of the segment.
/// </summary>
public char SegmentDelimiter
{
get;
set;
}
/// <summary>
/// Generates an X.12 formatted segment.
/// </summary>
/// <param name="sep">The field delimiter to use.</param>
/// <param name="eol">The segment delimiter to use.</param>
/// <returns>An X.12 formatted string.</returns>
public string ToString(char sep, char eol)
{
//Do cool stuff
}
/// <summary>
/// Generates an X.12 formatted segment.
/// </summary>
/// <returns>An X.12 formatted string.</returns>
public override string ToString()
{
//Do cool stuff
}
/// <summary>
/// Returns the Type associated with the Field enumeration of this object.
/// </summary>
/// <returns>A System.Type of this object's Field enumeration.</returns>
public virtual Type GetFieldEnum()
{
//Do cool stuff
}
}
}
Now, when I open up VBA and add the reference, the class shows up in IntelliSense. However, when I Dim a variable with the X12Segment type, then put in the dot operator, the IntelliSense window that pops up shows me that ToString() is a property, not a method. Also, ToString's overload shows up as ToString_2. When I attempt Set seg = New X12Segment, VBA tells me that it's an invalid use of the New keyword.
What am I missing here?
Update
I've revised my code as per the comments and answer below, and I have solutions for New not working and my ToString overload appearing funky in IntelliSense. A new problem arises, though; trying to access Fields gives me errors. seg.Fields = someStringArray gets me an error saying "Function or interface marked as restricted, or the function uses an Automation type not supported in Visual Basic".
A [ComVisible] class must have a parameterless constructor. COM doesn't have a mechanism to pass arguments to a constructor. You provided only constructors that take arguments which is why VBA doesn't let you use the New keyword.
COM also doesn't support method overloading. You need to give the methods distinct names. If you don't then the type library exporter will take care of it automatically. Thus ToString_2().
I'm using the data annotations attributes for validation in my app, and I want to have a RequiredAsSet attribute, which will require either all properties decorated with the attribute to be populated, or none of them. The set cannot be part populated.
I thought a clever way to do it would be like this:
public class RequiredAsSetAttribute : RequiredAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="RequiredAsSetAttribute"/> class.
/// </summary>
/// <param name="viewModel">The view model.</param>
public RequiredAsSetAttribute(IViewModel viewModel)
{
this.ViewModel = viewModel;
}
/// <summary>
/// Gets or sets the view model.
/// </summary>
/// <value>The view model.</value>
private IViewModel ViewModel { get; set; }
/// <summary>
/// Determines whether the specified value is valid.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>
/// <c>true</c> if the specified value is valid; otherwise, <c>false</c>.
/// </returns>
public override bool IsValid(object value)
{
IEnumerable<PropertyInfo> properties = GetPropertiesWihRequiredAsSetAttribute();
bool aValueHasBeenEnteredInTheRequiredFieldSet = properties.Any(property => !string.IsNullOrEmpty(property.GetValue(this.ViewModel, null).ToString()));
if (aValueHasBeenEnteredInTheRequiredFieldSet)
{
return base.IsValid(value);
}
return true;
}
/// <summary>
/// Gets the properties with required as set attribute.
/// </summary>
/// <returns></returns>
private IEnumerable<PropertyInfo> GetPropertiesWithRequiredAsSetAttribute()
{
return this.ViewModel.GetType()
.GetProperties()
.Where(p => GetValidatorsFromProperty(p).Length != 0 && !GetValidatorsFromProperty(p).Any(x => x == this));
}
/// <summary>
/// Gets the validators from property.
/// </summary>
/// <param name="property">The property.</param>
/// <returns></returns>
private static RequiredAsSetAttribute[] GetValidatorsFromProperty(PropertyInfo property)
{
return (RequiredAsSetAttribute[])property.GetCustomAttributes(typeof(RequiredAsSetAttribute), true);
}
}
It basically takes my view model as a constructor argument and uses reflection to find the other properties decorated with the RequiredAsSet attribute to check if anything has been entered.
Turns out this isn't such a clever idea though, because you can't pass instances into the constructor of an attribute..only constant expresssions, typeof expressions or array creation expressions, as the compiler helpfully pointed out.
So is there another way to do this?
If I understand the problem correcty, the way to do this is with a class level validation attribute. You then have access to the entire object and can use reflection to access whatever properties you wish. The instance is passed in during validation.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class RequiredAsSetAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var properties = TypeDescriptor.GetProperties(value);
...
}
}