I've been following a post regarding extension methods from this post:
public static IEnumerable<T> Distinct<T,TKey>(this IEnumerable<T> list, Func<T,TKey> lookup) where TKey : struct {
return list.Distinct(new StructEqualityComparer<T, TKey>(lookup));
}
class StructEqualityComparer<T,TKey> : IEqualityComparer<T> where TKey : struct {
Func<T, TKey> lookup;
public StructEqualityComparer(Func<T, TKey> lookup) {
this.lookup = lookup;
}
public bool Equals(T x, T y) {
return lookup(x).Equals(lookup(y));
}
public int GetHashCode(T obj) {
return lookup(obj).GetHashCode();
}
}
Could someone explain the purpose of the where TKey : struct appended to the extension method and comparator class. Removing these statements seems to make no difference to a simple test code - both evaluations TKey are of type int on a class and struct respectively:
public struct TestMeStruct
{
public int a;
public int b;
}
public class TestMeClass
{
public int a { get; set; }
public int b { get; set; }
}
public void Test()
{
List<TestMeStruct> lstruct = new List<TestMeStruct>();
lstruct.Add(new TestMeStruct() { a = 1, b = 2 });
lstruct.Add(new TestMeStruct() { a = 3, b = 7 });
lstruct.Add(new TestMeStruct() { a = 3, b = 14 });
lstruct.Add(new TestMeStruct() { a = 32, b = 11 });
List<TestMeClass> lclass = new List<TestMeClass>();
lclass.Add(new TestMeClass() { a = 1, b = 2 });
lclass.Add(new TestMeClass() { a = 3, b = 7 });
lclass.Add(new TestMeClass() { a = 3, b = 14 });
lclass.Add(new TestMeClass() { a = 32, b = 11 });
var one = lstruct.Distinct(mem => mem.a).ToList();
var two = lclass.Distinct(mem => mem.a).ToList();
}
Both return identical lists. Much obliged for any clarity on what's going on!
From msdn
The where clause is used to specify constraints on the types that can
be used as arguments for a type parameter defined in a generic
declaration. For example, you can declare a generic class,
MyGenericClass, such that the type parameter T implements the
IComparable interface:
public class MyGenericClass where T:IComparable { }
I believe you know what is generic type constraints and especially where : struct, in short -by specifying such constraint you indicate that only value types could be used as generic type parameter, for instance int, double, etc.
In current implementation of Distinct and StructEqualityComparer this really does not make any difference, but idea of StructEqualityComparer is to compare structs, its name saying this, so all classes/method which are passing through own generic type parameters required for StructEqualityComparer obligated redefine the same generic type constraints as well. In your case Distinct() method passign its ownt T parameter so obligated to redefine all constraints.
The where keyword is used to qualify a generic parameter. In this case, you are indicating that the StructEqualityComparer must take something that is a value type (or struct), so that an equality comparison will compare by value rather than by reference.
I am pretty sure you are not asking what 'where' means. You are asking 'what is the point in putting it there'.
In the original post that you linked to, Sam Saffron said "A similar helper class can be built to compare objects. (It will need to do better null handling)". So, with your little test suite nothing goes wrong, because you are not passing any nulls. Try passing nulls, and it will blow up.
What probably happened is that Sam Saffron wrote an EqualityComparer, and then realized that he should be checking for nulls all over the place, which would make his code kind of unsuitable as an example, so instead of adding null checks he just renamed it to StructEqualityComparer and he made it only work with structs.
Related
Now I have a SomeClass<T> with a constructor SomeClass(IList<T?> list). But when I use a List<int?> to construct it, the compiler told me:
Cannot resolve constructor
SomeClass(System.Collections.Generic.List<System.Nullable<int>>),
candidates are: SomeClass(System.Collections.Generic.IList<int>)
I find it means that I have to add "struct" to T's base class list to make sure that T is a value type, but why does this happen and how can make this class avoid using only value type as generic parameter?
#canton7 explains why this does not work in a comment.
You cannot solve this with a constructor, as you will have to introduce a new type parameter and constructors cannot declare type parameters. Therefore I suggest using a factory method.
public class SomeClass<T>
{
public IList<T> List { get; private set; }
public static SomeClass<T> Create<U>(IList<Nullable<U>> list)
where U : struct, T
{
return new SomeClass<T> {
List = list
.OfType<T>()
.ToList()
};
}
}
Introducing the type parameter U allows us to add a type constraint that is limited to this method, while the type parameter T of the class remains unconstrained.
Note that a null value does not have a type. Therefore, OfType<T>() filters out nulls.
You can test it like this:
var ints = new List<int?> { 1, 2, null, 3 };
var sut = SomeClass<int>.Create(ints);
foreach (int item in sut.List) {
Console.WriteLine(item);
}
Prints:
1
2
3
Is there a way in C# to perform a user defined conversion for generic types ?
for example:
class Stack<T>
{
private T x; //should be an array but doesn't matter for this example
public Stack(T input)
{
x = input;
}
public Stack<Q> Convert<Q>(Stack<T> inputStack)
{
//what would go here ? The call is below.
}
}
//main code
Stack<int> stack = new Stack<int>(2);
Stack<long> longstack = stack.Convert<long>(stack);
I would imagine that the compiler can deduce that Q is long and T is int in the Convert function, but it doesn't seem to work.
No, because class-level generic type parameters can't be auto-infered from usage.
I would imagine that the compiler can deduce that Q is long and T is
int in the Convert function, but it doesn't seem to work.
Maybe, but at the end of the day, a generic type parameter doesn't belong to constructor. That is, you provide a generic argument to the type based on what constructor parameter/argument? What happens if there're more than a constructor parameter?
public class A<T>
{
// Which one should be used to auto-infer T from usage?
// Maybe the integer? Or the bool? Or just the string...?
// Every choice seems a joke, because it would be absolutely
// arbitrary and unpredictable...
public A(int x, string y, bool z)
{
}
}
Now take your sample code as example. It has the same issue: what argument should be used from your Convert static method to infer generic type argument from usage? What happens if Convert has more than an argument...?
Here is extension method for standart Stack class (you can rewrite it a little and use like instance method in your own Stack class):
public static class MyStackExtensions
{
public static Stack<TDest> Convert<TSrc, TDest>(
this Stack<TSrc> stack,
Func<TSrc, TDest> converter = null)
{
if (stack == null)
throw new ArgumentNullException("stack");
var items = converter == null
? stack.Select(i => (TDest) System.Convert.ChangeType(i, typeof (TDest)))
: stack.Select(converter);
return new Stack<TDest>(items.Reverse());
}
}
Convert stack from int to long using convert function - no type params needed :-)
var intStack = new Stack<int>(new[] { 1, 2, 3 });
var longStack = intStack.Convert(i => (long)i);
Or using standart conversion:
var intStack = new Stack<int>(new[] { 1, 2, 3 });
var longStack = intStack.Convert<int, long>();
I have a number of classes that derive from a class BaseClass where BaseClass just has an `Id property.
I now need to do distinct on a collections of some of these objects. I have the following code over and over for each of the child classes:
public class PositionComparer : IEqualityComparer<Position>
{
public bool Equals(Position x, Position y)
{
return (x.Id == y.Id);
}
public int GetHashCode(Position obj)
{
return obj.Id.GetHashCode();
}
}
Given the logic is just based on Id, I wanted to created a single comparer to reduce duplication:
public class BaseClassComparer : IEqualityComparer<BaseClass>
{
public bool Equals(BaseClass x, BaseClass y)
{
return (x.Id == y.Id);
}
public int GetHashCode(BaseClass obj)
{
return obj.Id.GetHashCode();
}
}
But this doesn't seem to compile:
IEnumerable<Position> positions = GetAllPositions();
positions = allPositions.Distinct(new BaseClassComparer())
...as it says it can't convert from BaseClass to Position. Why does the comparer force the return value of this Distinct() call?
UPDATE: This question was the subject of my blog in July 2013. Thanks for the great question!
You have discovered an unfortunate edge case in the generic method type inference algorithm. We have:
Distinct<X>(IEnumerable<X>, IEqualityComparer<X>)
where the interfaces are:
IEnumerable<out T> -- covariant
and
IEqualityComparer<in T> -- contravariant
When we make the inference from allPositions to IEnumerable<X> we say "IEnumerable<T> is covariant in T, so we can accept Position or any larger type. (A base type is "larger" than a derived type; there are more animals than giraffes in the world.)
When we make the inference from the comparer we say "IEqualityComparer<T> is contravariant in T, so we can accept BaseClass or any smaller type."
So what happens when it comes time to actually deduce the type argument? We have two candidates: Position and BaseClass. Both satisfy the stated bounds. Position satisfies the first bound because it is identical to the first bound, and satisfies the second bound because it is smaller than the second bound. BaseClass satisfies the first bound because it is larger than the first bound, and identical to the second bound.
We have two winners. We need a tie breaker. What do we do in this situation?
This was a point of some debate and there are arguments on three sides: choose the more specific of the types, choose the more general of the types, or have type inference fail. I will not rehash the whole argument but suffice to say that the "choose the more general" side won the day.
(Making matters worse, there is a typo in the spec that says that "choose the more specific" is the right thing to do! This was the result of an editing error during the design process that has never been corrected. The compiler implements "choose the more general". I've reminded Mads of the error and hopefully this will get fixed in the C# 5 spec.)
So there you go. In this situation, type inference chooses the more general type and infers that the call means Distinct<BaseClass>. Type inference never takes the return type into account, and it certainly does not take what the expression is being assigned to into account, so the fact that it chooses a type that is incompatible with the assigned-to variable is not it's business.
My advice is to explicitly state the type argument in this case.
If you look at the definition of Distinct there is only one generic type parameter involved (and not one TCollection used for input and output collections and one TComparison for the comparer). That means that your BaseClassComparer constrains the result type to base class and the conversion at the assignment is not possible.
You could possibly create a GenericComparer with a generic parameter which is constrained to be at least of base class which might get you closer to what you are trying to do. This would look like
public class GenericComparer<T> : IEqualityComparer<T> where T : BaseClass
{
public bool Equals(T x, T y)
{
return x.Id == y.Id;
}
public int GetHashCode(T obj)
{
return obj.Id.GetHashCode();
}
}
Because you need an instance and not just a method call you can't let the generic type be inferred by the compiler (see this discussion) but have to do so when creating the instance:
IEnumerable<Position> positions;
positions = allPositions.Distinct(new GenericComparer<Position>());
Eric's answer explains the root cause of the whole issue (in terms of covariance and contravariance).
Imagine if you had:
var positions = allPositions.Distinct(new BaseClassComparer());
What would you expect the type of positions to be? As compiler deduces from argument given to Distinct which implements IEqualityComparer<BaseClass>, the type of the expression is IEnumerable<BaseClass>.
That type can't be automatically converted to IEnumerable<Position> so compiler produces an error.
Since IEqualityComparer<T> is contravariant in the type T, you can use the base class comparer with distinct if you specify the generic parameter to Distinct:
IEnumerable<Position> distinct = positions.Distinct<Position>(new BaseClassComparer());
If you don't specify this, the compiler infers the type of T to be BaseClass since BaseClassComparer implements IEqualityComparer<BaseClass>.
You need small changes in your code. Bellow working example:
public class BaseClass
{
public int Id{get;set;}
}
public class Position : BaseClass
{
public string Name {get;set;}
}
public class Comaprer<T> : IEqualityComparer<T>
where T:BaseClass
{
public bool Equals(T x, T y)
{
return (x.Id == y.Id);
}
public int GetHashCode(T obj)
{
return obj.Id.GetHashCode();
}
}
class Program
{
static void Main(string[] args)
{
List<Position> all = new List<Position> { new Position { Id = 1, Name = "name 1" }, new Position { Id = 2, Name = "name 2" }, new Position { Id = 1, Name = "also 1" } };
var distinct = all.Distinct(new Comaprer<Position>());
foreach(var d in distinct)
{
Console.WriteLine(d.Name);
}
Console.ReadKey();
}
}
I am making a comparer for set operation on various types.
So I have a generic class
public class Comparer<T, Tid>
...
public bool Equals(T x, T y)
{
var xid = m_idfunc(x);
var yid = m_idfunc(y);
return (Tid)xid == (Tid)yid;
}
Where m_idfunc is a lambda passed in to the Comparer constructor, it is
Func<T,Tid>
I create a comparer with Tid = string. I get in the equals function xid = string1, yid = string2
If string1 and string 2 are the same ("foo" and "foo" say)
xid == yid
yields false
(Tid)xid == (Tid)yid
also yields false (it should not be necessary - I was just getting desperate)
heres my immediate window - paused on the return xid == yid line
yid.GetType() == typeof(string)
true
xid.GetType() == typeof(string)
true
xid==yid
false
(string)xid==(string)yid
true
xid.Equals(yid)
true
Whats going on?
What’s interesting about this is that it might just work the way you want it to. Here’s an example:
using System;
using System.Text;
namespace ConsoleApplication1 {
class Program {
public static void Main() {
string myString = "1";
object objectString = "1";
string myCopiedString = string.Copy(myString);
string internedString = string.Intern(myCopiedString);
Console.WriteLine(myString); //1
Console.WriteLine(objectString); //1
Console.WriteLine(myCopiedString); //1
Console.WriteLine(internedString); //1
Console.Write(objectString == myString); //true
Console.Write(objectString == "1"); //true
Console.Write(objectString == myCopiedString); //!!!FALSE!!!!
Console.Write(objectString == internedString); //true
Console.Write(objectString == SomeMethod()); //!!!FALSE!!!
Console.Write(objectString == SomeOtherMethod()); //true
}
public static string SomeMethod() {
StringBuilder sb = new StringBuilder();
return sb.Append("1").ToString();
}
public static string SomeOtherMethod() {
return "1".ToString();
}
}
}
The reason why it might work is due to string interning. So, this is definitely one to watch out for, because it can actually work when you test it, but depending on the implementation, it might suddenly break.
In your case, you need to determine whether you care about Reference equality or "value" equality. == is reference equality, which again, depending on whether or not the string is interned may be true. I suspect you actually want to use EqualityComparer<T>.Default.Equals in your function.
If you run open this in VS you'll see the compiler warning: “Possible unintended reference comparison; to get a value comparison, cast the left hand side to type 'string'”. In your case however, the compiler can't warn you, because as far as it knows, the types are objects, it doesn't know that one or both are string.
My initial assumption was that because it's generics, it can't do a reference to value conversion behind the scenes that it does for strings. I wanted to put together an example that supported this. :)
I had to make a few assumptions to put something together for this so my example may not be 100% on. (code I used is at the bottom)
I wasn't able to get anything to compile when I just had
class Comparer<T, TId>
{
private readonly Func<T, TId> m_idfunc;
public Comparer(Func<T, TId> idFunc)
{
m_idfunc = idFunc;
}
public bool Equals(T x, T y)
{
var xid = m_idfunc(x);
var yid = m_idfunc(y);
return (TId)xid == (TId)yid;
}
}
I found https://stackoverflow.com/a/390919/156708 and modifed the class declaration to be
class Comparer<T, TId> where TId : class
and it compiled. Step 1.
I set up the Equals function as
public bool Equals(T x, T y)
{
var xid = m_idfunc(x);
var yid = m_idfunc(y);
return (TId)xid == (TId)yid;
}
And the result is False (see full code for generation of value in xid|yid). Fitting my assumption that Generics has a hand in this. Not enough yet, need to see what happens if the Generics aspect is removed.
Changing the Comparer class to be
class Comparer<T>
{
private readonly Func<T, string> m_idfunc;
public Comparer(Func<T, string> idFunc)
{
m_idfunc = idFunc;
}
public bool Equals(T x, T y)
{
var xid = m_idfunc(x);
var yid = m_idfunc(y);
return xid == yid;
}
}
returns True.
I'm not 100% on this, but my assumption is based on the fact that the == operator of the string class does a value check instead of a reference check. When using generics, it is likely setting up to only do reference checks (haven't dug into the IL to see what it's doing there), and if the string locations in memory are not the same then it will return false. (I'm kinda winging the details, as I don't know them prezactly, just a working hypothesis that seems to be working)
My complete sample code with generics is below.
using System;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
var compare = new Comparer<Example, string>(example => example.id(example));
var ex1 = new Example();
var ex2 = new Example();
Console.WriteLine(compare.Equals(ex1, ex2));
Console.ReadLine();
}
class Example
{
public string id(Example example)
{
return new string(new [] {'f', 'o', 'o'});
}
}
class Comparer<T, TId> where TId : class
{
private readonly Func<T, TId> m_idfunc;
public Comparer(Func<T, TId> idFunc)
{
m_idfunc = idFunc;
}
public bool Equals(T x, T y)
{
var xid = m_idfunc(x);
var yid = m_idfunc(y);
return (TId)xid == (TId)yid;
}
}
}
}
Hope that helps... and that I'm not terribly wrong on my reasoning. :)
I think, it is more correct to use EqualityComparer<TId> inside Comparer<T, Tid>. Besides, instead of delegate I would use interface to get identifiers:
interface IObjectWithId<T>
{
T Id { get; }
}
class IdEqualityComparer<T, TId> : EqualityComparer<T>
where T : IObjectWithId<TId>
{
public override bool Equals(T x, T y)
{
return EqualityComparer<TId>.Default.Equals(x.Id, y.Id);
}
public override int GetHashCode(T obj)
{
return EqualityComparer<TId>.Default.GetHashCode(obj.Id);
}
}
class A : IObjectWithId<string>
{
public string Id { get; set; }
}
Usage:
var a = new A { Id = "foo" };
var b = new A { Id = "foo" };
var c = new A { Id = "bar" };
var comparer = new IdEqualityComparer<A, string>();
Console.WriteLine(comparer.Equals(a, b)); // true
Console.WriteLine(comparer.Equals(a, c)); // false
The C operator "==" has two very different meanings. It can either invoke a type-specific overloaded equality-operator method if the compiler can statically determine that such a method is applicable to the operand types, or it can perform a reference comparison between the operands, if both operands are known to be reference types and there may exist an object which could be referred to by both operands. For most types, only one type of comparison would be possible; value types do not support reference comparison, and most reference types do not overload the equality operator. There is, however, a common class which would support both types of comparison: System.String.
The vb.net language avoids ambiguity here by only allowing the = operator to be used on types which overload it. For reference comparisons, the Is operator is required. If one were to attempt to write your code in vb.net, the = operator would not be permitted on class-constrained generics. One could use the Is operator, but it would check for reference equality regardless of whether the operands overload =.
As it is, in C#, assuming you have a class constraint on your generic type (the == operator won't work without it), the compiler can only use an overloaded equality operator on a generic type if the type is constrained to one for which the operator is overloaded. Since you don't constrain your generic type parameter to string (indeed, since string is sealed, the compiler won't allow such a constraint) there's no way the compiler can use the string overload of the equality operator. Thus, it uses the version of the equality operator that it knows is available on a class-constrained generic--reference equality (equivalent to the Is operator in vb.net).
If I have the following enum:
public enum ReturnValue{
Success = 0,
FailReason1 = 1,
FailReason2 = 2
//Etc...
}
Can I avoid casting when I return, like this:
public static int main(string[] args){
return (int)ReturnValue.Success;
}
If not, why isn't an enum value treated as an int by default?
enums are supposed to be type safe. I think they didn't make them implicitly castable to discourage other uses. Although the framework allows you to assign a constant value to them, you should reconsider your intent. If you primarily use the enum for storing constant values, consider using a static class:
public static class ReturnValue
{
public const int Success = 0;
public const int FailReason1 = 1;
public const int FailReason2 = 2;
//Etc...
}
That lets you do this.
public static int main(string[] args){
return ReturnValue.Success;
}
EDIT
When you do want to provide values to an enum is when you want to combine them. See the below example:
[Flags] // indicates bitwise operations occur on this enum
public enum DaysOfWeek : byte // byte type to limit size
{
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64,
Weekend = Sunday | Saturday,
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday
}
This enum can then be consumed by using bitwise math. See the below example for some applications.
public static class DaysOfWeekEvaluator
{
public static bool IsWeekends(DaysOfWeek days)
{
return (days & DaysOfWeek.Weekend) == DaysOfWeek.Weekend;
}
public static bool IsAllWeekdays(DaysOfWeek days)
{
return (days & DaysOfWeek.Weekdays) == DaysOfWeek.Weekdays;
}
public static bool HasWeekdays(DaysOfWeek days)
{
return ((int) (days & DaysOfWeek.Weekdays)) > 0;
}
public static bool HasWeekendDays(DaysOfWeek days)
{
return ((int) (days & DaysOfWeek.Weekend)) > 0;
}
}
There is no implicit cast because the enum does not have to use int as the underlying type. If your enum used a uint as the underlying type, for instance, there is no implicit cast from uint to int.
The c# enum is useless.
You can avoid casting from your type AND constrain the values that can be explicitly cast to your type by making a sealed class, and providing implicit/explicit conversion operators.
Provide an implicit operator for converting from your type to a generic int so you don't have to cast.
Provide an explicit operator for converting from an int to your type, which throws an error if the integer fails to meet the constraint, such as (int x) => (x >= 0 && x <= 2).
If using this technique, create a generic immutable base class such as ConstrainedNumber<T>, which has a constructor that accepts a T value and delegate for the constraint: delegate bool NumberConstraint<T>(T value). The constructor should run the value through the constraint delegate, and throw an exception if it fails to meet the constraint. The base class should also take care of the implicit conversion operation to T, and should handle equality by overloading object.Equals(object) and object.GetHashCode(), defining == and != operators for the type ConstrainedNumber<T>, and implementing IEquatable<T> and IEquatable<ConstrainedNumber<T>>. I also recommend defining an copy constructor for the base class, and all derived types. Cloning can then be implemented cleanly in the base class by retrieving the copy constructor via reflection, but this is entirely optional. You can figure out the ConstrainedNumber<T> implementation yourself, unless I've already posted it on stackoverflow somewhere.
You can provide named static readonly values in your derived ConstrainedNumber, so that you can access them just like an enum.
public sealed class ReturnValue: ConstrainedNumber<int>
{
public static readonly NumberConstraint<int> constraint = (int x) => (x >= 0 && x < 3);
public static readonly ReturnValue Success = new ReturnValue(0);
public static readonly ReturnValue FailReason1 = new ReturnValue(1);
public static readonly ReturnValue FailReason2 = new ReturnValue(2);
private ReturnValue( int value ): base( value, constraint ) {}
private ReturnValue( ReturnValue original ): base (original) {} //may be used to support IClonable implementation in base class
public static explicit operator ReturnValue( int value )
{
switch(value) //switching to return an existing instance is more efficient than creating a new one and re-checking the constraint when there is a limited number of allowed values; if the constraint was more generic, such as an even number, then you would instead return a new instance here, and make your constructors public.
{
case 0: return Success;
case 1: return FailReason1;
case 2: return FailReason2;
}
throw new ArgumentException( "Value fails to meet the constraint defined for " + typeof(ReturnValue).FullName + ".", "value" );
}
}
You could use this technique for any constraint. For example, a class called EvenNumber may have a constraint that returns true if the given number is even. In that case, you'd just make your constructors public, and simplify your static conversion operator to just return a new EvenNumber, instead of switching to return one of the limited existing instances.
It could be used like this:
EvenNumber x = (EvenNumber)2;
EvenNumber y = (EvenNumber)3; //throws exception "Value fails to meet the constraint defined for {namespace}.EvenNumber." A c# enum would stupidly allow such a cast, creating an invalid EvenNumber, breaking the object-oriented model
int z = x; //implicit conversion, no cast necessary;
Enums and ints are simply not implicitly castable as per the spec (except for the literal 0, which is allowed for comparison tests / assignments / etc). The explicit cast is all that is needed, though.
Strangely enough, this is not specific to the .NET Framework, but just to C#. As the other commenters have already pointed out, in C# this is basically a specification of the language. The same is not true in VB.NET.
Check out the MSDN reference page for Enums in VB.NET. Note that you can specify the data type of an enumeration at Enum declaration time.
That means, if you really don't want to litter your code with casts to (int), you could write your enumeration in VB.NET, declare it as an integer, then use that Enum from C#.
Remember how they told us computers would make our lives so much simpler? :)
No, you can't avoid casting; as to why there's no implicit conversion, I don't know, but there's not.
You can ascribe this behaviour to the basic intention behind creating Enumerations... to create a set of named constants that can only have specified (or default) values depending on the underlying type.
There are two separate issues to consider, as related to your question:
An Enum value cannot be treated as an int by default because then you would be able to provide any integer and there would be no compile time check to validate that the provided integer does in fact exist as a value in the Enumeration.
Casting becomes necessary since you are trying to convert from the governing type (of type YourCustomEnum which derives from the System.Enum class) to the underlying type, i.e., int or byte, etc.
Risking a Necromancer batch, I still like to post a possibility that didn't come up yet: To use a helper class (resp. struct) that converts implicitly into int and the enum type:
internal struct AutoCaster<T1, T2> {
private T1 Value1 { get; }
private T2 Value2 { get; }
public AutoCaster(T1 value1) {
Value1 = value1;
Value2 = (T2)(object)value1;
}
public AutoCaster(T2 value2) {
Value1 = (T1)(object)value2;
Value2 = value2;
}
public static implicit operator AutoCaster<T1, T2>(T2 input) {
return new AutoCaster<T1, T2>(input);
}
public static implicit operator AutoCaster<T1, T2>(T1 input) {
return new AutoCaster<T1, T2>(input);
}
public static implicit operator T1(AutoCaster<T1, T2> input) {
return input.Value1;
}
public static implicit operator T2(AutoCaster<T1, T2> input) {
return input.Value2;
}
}
As the Main needs a fix return type (int or void) it does not look that elegant in your example, but for other purposes it works just fine:
public static int Main(string[] args) {
return Main2(args);
}
private static AutoCaster<int, ReturnValue> Main2(string[] args) {
return ReturnValue.FailReason2;
}
How about using static Members of a Class?
//enum DocInfos { DocName, DocNumber, DocVersion};
public class DocInfos
{
public static int DocName = 0;
public static int DocNumer = 1;
public static int DocVersion = 2;
}
...
Doc = new string[DocInfos.DocVersion];
// Treffer
Doc[DocInfos.DocName] = TrimB(HTMLLines[lineCounter + 2])
...