Click here to Skip to main content
15,881,861 members
Please Sign up or sign in to vote.
4.63/5 (8 votes)
So I'm having a small problem when working with numerics. I can't really imagine that I am the only one with this problem, yet I can't find any solutions. So here it goes.

I am making an Attribute[^] which takes a numeric in its constructor (ANY numeric). Now here is the problem, there are 11(!) different types of numerics... Byte[^], SByte, Int16[^], UInt16[^], Double[^], Decimal[^]... You get the point.
So am I doomed to create 11 overloaded constructors (actually 22 since my constructor already had an overload)?
And what do I do with the numeric once it has been passed to my constructor? Do I also create 11 private fields that can hold each numeric or should I box it in an Object[^]?

In any case I will need a huge Case[^]/switch[^] statement later on to check for type of the numeric...
Any solutions for this problem? For example, taking an Int64[^] and UInt64[^] could take care for all non-decimal numerics, but I am not sure if this is good/accepted practice.

Thanks.
Posted
Updated 27-May-12 4:00am
v2
Comments
Sandeep Mewara 27-May-12 10:46am    
5!
Sander Rossel 27-May-12 11:09am    
Thanks!
Mehdi Gholam 27-May-12 10:50am    
5'ed for the question.
Sander Rossel 27-May-12 11:09am    
Thank you.
burndavid93 27-May-12 11:18am    
my 5.

Usually attributes are processed once (type interrogation etc.) so performance is not critical so boxing is a viable option which reduces your code size.

I am afraid you can not escape a big switch statement [or a nested IF] (if you can let me know! I have tried :) [DataTypes.cs in RaptorDB Doc store]).
 
Share this answer
 
Comments
Sander Rossel 27-May-12 11:17am    
Actually these Attributes are processed quite a lot! Let's say once every few seconds... Basically every time the BindingCompleted Event of a Binding is raised and the Property that triggered it had the Attribute.
Sander Rossel 28-May-12 10:49am    
Hi Mehdi. Unfortunately your answer didn't quite help, but thanks for a good effort.
I have found a way to escape a big switch statement by using IComparable. In my specific case I do not need to add, subtract etc. so as long as I know they're numerics (and I do, since my constructors only allow for numerics) I can safely compare them :)
Mehdi Gholam 28-May-12 10:51am    
Cheers
As far as I can see there is no way to have one single constructor. Using a ValueType[^] is an idea, but not very user friendly (since ValueType[^] can be more than just numerics, in which case I would have to throw an Exception[^]). By storing the numerics into a Private field as an IComparable[^] (as all numeric types are IComparable[^]) I can then simply call IComparable.CompareTo[^] to compare the value with whatever other numeric I get. This saves me the huge case statement (I am not interested in type anymore, since I can compare anyway).
Of course this only works because I do not have to add, subtract etc. and only need to compare. Any other tips, tricks, solutions etc. are still welcome. Especially on how I could still add, subtract etc. using only a single type and not 11...

Thanks everyone for the help so far!
 
Share this answer
 
v3
How about dynamic?
If you are willing to pay the run-time cost, and if you are willing to give up on compile-time type check, dynamic could help.

C# really lacks generics specialization and (especially if run-time performance matters) compile-time elaboration of generics.

In C++ you can build type traits based on templates that were elaborated at compile-time (e.g. a pioneering book on that was and still is Modern C++ Design[^]).

The concept is based on three aspects:

  1. functional programming style for template programming
  2. define default behavior in a templated item, add specialization for the types in question (e.g. for the several numeric types)
  3. C++ allows to have values as template parameters (e.g. not only types but also compile-time constants)


Well, C# does not get even close to this :-(

Top be honest, I really dislike this "workaround", but you have at least another option to evaluate for your problem at hand.

Cheers
Andi

PS: here goes some sample code:
C#
[AttributeUsage(AttributeTargets.Class)]
public class MyAttrAttribute : Attribute
{
    public dynamic Arg { get; private set; } // dynamic attribute
    public MyAttrAttribute(object arg)
    {
        Arg = arg;
    }
    public static MyAttrAttribute GetAttribute(Type owner)
    {
        return (from a in owner.GetCustomAttributes(false)
                where a.GetType() == typeof(MyAttrAttribute)
                select a
                ).FirstOrDefault() as MyAttrAttribute;
    }
}

[MyAttr(123)]
public class MyClassWithIntAttr
{
    public MyClassWithIntAttr()
    {
        var attr = MyAttrAttribute.GetAttribute(
                     MethodBase.GetCurrentMethod().DeclaringType);
        Console.WriteLine("{0} = {1}", attr, attr.Arg + 42); // may throw exception
    }
}
[MyAttr(3.14159265358)]
public class MyClassWithDoubleAttr
{
    public MyClassWithDoubleAttr()
    {
        var attr = MyAttrAttribute.GetAttribute(
                     MethodBase.GetCurrentMethod().DeclaringType);
        Console.WriteLine("{0} = {1}", attr, attr.Arg + 42); // may throw exception
    }
}

class Program
{
    public static void Main()
    {
        MyClassWithIntAttr c1 = new MyClassWithIntAttr();
        MyClassWithDoubleAttr c2 = new MyClassWithDoubleAttr();
    }


Output:

Sample.MyAttrAttribute = 165
Sample.MyAttrAttribute = 45.14159265358
 
Share this answer
 
Comments
Wonde Tadesse 28-May-12 16:20pm    
5+
Andreas Gieriet 28-May-12 16:25pm    
Thanks!
Andi
Sander Rossel 29-May-12 13:44pm    
Thanks for a fine answer. However, I am wondering how I could get this result in VB.NET?
It seems this is a C# only solution, correct?
In any case you have my 5 for this interesting C# solution :)
Andreas Gieriet 30-May-12 2:39am    
No idea about VB.Net. Google tells me that VB.Net does not know that feature. C# only knows that since C# 4.0. See solution #5 for an alternative.
Cheers
Andi
As a followup to solution #4: if you don't have the dynamic feature at hand (VB.Net, .Net version before 4.0), you may employ casts (see the As<T>(...) method):

C#
[AttributeUsage(AttributeTargets.Class)]
public class MyAttrAttribute : Attribute
{
    private object _arg;
    public MyAttrAttribute(object arg)
    {
        _arg = arg;
    }
    // cast to a needed type
    public static T As<T>(Type owner)
    {
        var attr = (from a in owner.GetCustomAttributes(false)
                where a.GetType() == typeof(MyAttrAttribute)
                select a
                ).FirstOrDefault() as MyAttrAttribute;
        return attr != null ? (T)attr._arg : default(T);
    }

}

[MyAttr(123)]
public class MyClassWithIntAttr
{
    public MyClassWithIntAttr()
    {
        int value = MyAttrAttribute.As<int>(this.GetType());
        Console.WriteLine("attr + 42 = {0}", value + 42);
    }
}
[MyAttr(3.14159265358)]
public class MyClassWithDoubleAttr
{
    public MyClassWithDoubleAttr()
    {
        double value = MyAttrAttribute.As<double>(this.GetType());
        Console.WriteLine("attr + 42 = {0}", value + 42);
    }
}
class Program
{
    public static void Main()
    {
        MyClassWithIntAttr c1 = new MyClassWithIntAttr();
        MyClassWithDoubleAttr c2 = new MyClassWithDoubleAttr();
    }
}


Output:
attr + 42 = 165
attr + 42 = 45.14159265358


Cheers
Andi
 
Share this answer
 
Comments
Sander Rossel 30-May-12 11:38am    
Hi Andi and thanks for another answer. Really very interesting. The problem here though is that in order to use As<t> you have to know the type of the numeric, and that's exactly what I don't have :)
Of course I could always use an int64 (anything from byte to int64), decimal (decimal) or double (double or float), since any numeric goes in those three types. In that case this would work pretty well. Although you'd still have a case (case decimal, case double, float, case else).
Pretty sweet solution! And thanks again.
Andreas Gieriet 30-May-12 13:14pm    
Hi, Naerling,

you are welcome!

I'm not really a fan of casting and storing items as object. So, I'd like to provide a better solution to this, but I have no clear idea what exactly you want to achieve. If you have an attrribute and pass *any* kind of numeric to it, what function/operation do you want to do on these different values? E.g. if you want to add some other value to it, you can only do that on values that can be implicitly converted one to the other.

Can you please provide a sample (pseudo) code showing how you would like to access the attribute's numeric value? That would help to find a suitable solution.

Cheers
Andi
Sander Rossel 30-May-12 13:39pm    
Actually, the only thing I need to do is compare one to the other. Since all numerics are IComparable I could store them as such (see my own answer to this question).
Basically what I wanted is to build something that takes a numeric as parameter. I wanted each numeric to be valid as input (let's say memory is really an issue and all we can use is int16). Now at run-time I am getting an object that holds a numeric of the same type. All I need to do now is cast the object to the correct numeric and check if one is greater than the other.

// excuse my c# skills, it's not my first language :)
public class Test
{
Int16 _int16Field;
Int32 _int32Field;

public Test(Int16 numeric){ _int16Field = numeric; }
public Test(Int32 numeric){ _int32Field = numeric; }
// etc...

bool IsObjectGreater(object numeric)
{
// cast numeric to the correct type.
if (typeof(numeric) == gettype(Int16)){ return (Int16)numeric > _int16Field; }
elseif (typeof(numeric) == gettype(Int32)) { return (Int32)numeric > _int32Field; }
// etc...
}
}

This is a lot of work and not a very elegant solution. It doesn't even work since all the fields are put on the stack anyway and just using an Int64 would have been more efficient memory wise.
One solution is to have only Int64, decimal and double since those numerics fit all other numerics.
What I have now is one field which stores the numeric (coming from one of the 11 overloaded constructors) as IComparable.
Now I only need to do the following:

bool IsObjectGreater(object numeric)
{
// from the top of my head I am not quite sure if < or > should be used.
return _comparableField.Compare(numeric) < 0;
}

Easier would have been if we just had one numeric type that changed its underlying type dynamically, depending on the size of the numeric. Although I'm not sure how that would go memory or performance wise...

numeric n = 254; // underlying type is now byte, since byte is sufficient.
n += 2; // underlying type is now int16, since byte does not suffice anymore, but int16 is the next best thing.

At least that's better than checking for 11 types max or at least three...
Andreas Gieriet 30-May-12 18:04pm    
There are several aspects in this whole thread that I'll try to address.
1) Your initial post talks about Attributes having overloaded numeric constructors.
2) Your motivation on the different sizes seem to be memory consumption.

Combining the two:
1) Attributes are stored on code, not on variables. I.e. no matter if you have one instance of a type or 1000000, an attribute exists only *once* on the definition of the class, method, etc. As a consequence, memory consumption is completely irrelevant.

2) I would say: if memory consumption is a *real* concern, then C#/VB.Net are not the right languages/environments. You have little control over the memory layout and size of objects - there is not even a sizeof operator of managed objects (you may use unsafe code blocks, though). See http://msdn.microsoft.com/en-us/library/eahchzkf(v=vs.100).aspx .

There is a way to "simulate" C/C++ unions (see http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/60150e7b-665a-49a2-8e2e-2097986142f3/), but still, the union uses up at least as much memory as the largest object in the union eats up.

Again, it's not worth the effort to spare memory on that scale in managed code.

Now that I have "discussed away" the memory consumption as reasons for (magically) optimizing numeric type sizes, the question remain, if there is further need to have a general "numeric" type?

I challenge that: computer arithmetics has its intrinsic limits (precision, accuracy, number ranges, etc.), and since these limits differ by built-in numeric type, you must do a prudent decision on which numeric type matches your needs. This decision can not be made by the compiler nor by the environment.

If looking from a bit further away, you might invent our own "numeric" class tree that optimizes somehow memory, but maybe to the cost of performance. I would never ever do that, though: too much hassle for "nothing" - it becomes very soon very complex if you want a behavior like built-in types: operators, conversions within the type tree, Math library support, conversion from/to built-in types, ... (you gonna die).

My advise:
1) analyze the problem domain and decide which numeric type solves *your* problem
2) use that type only
3) document your decision and the resulting limits

Cheers
Andi
Sander Rossel 30-May-12 18:28pm    
Best advice I've seen so far I guess.
The memory was just a theoretical example. I'm not really concerned by the memory that takes up a byte or an Int64... Not on modern computers anyway. I was simply curious to the limitations and possibilities of the different numeric types when comparing them to each other. It doesn't seem easy. Your solution 'decide which numeric type solves your problem and use that type only' saves a lot of headache though.
Thanks for the time and effort in making your answers. They have helped me a lot and it's really appreciated! :)
Have you considered using ValueType[^] as argument type of your constructor? As it is a larger subset than numerics, the utility in the above msdn article could be also of use.
 
Share this answer
 
Comments
Sander Rossel 27-May-12 16:24pm    
Interesting idea. I might just do that. I won't accept this as a final answer (yet), but have a 5 for the good idea!
Zoltán Zörgő 27-May-12 16:54pm    
Just give it a try. Thanks for the vote.
Sander Rossel 28-May-12 10:45am    
Accepted your answer as it gave me the idea for my own answer (using 11 overloads for a user friendly interface, but storing everything into an IComparable field). Thanks for the help! :)
Wonde Tadesse 28-May-12 16:18pm    
5+
Zoltán Zörgő 28-May-12 17:49pm    
Thank you

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900