Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does using C# structs (value types) sacrifice performance?

I have been playing with structs as a mechanism to implicitly validate complex value objects, as well as generic structs around more complex classes to ensure valid values. I am a little ignorant as to the performance consequences, so I am hoping you all can help me out. For example, if I were to do something like injecting a domain object into a value type wrapper, would that cause problems ? Why? I understand the difference between value types and reference types, and my goal here is to leverage the different behavior of value types. What exactly do I need to look into in order to do this responsibly?

Here is an extremely basic idea of something I was thinking.

public struct NeverNull<T>
    where T: class, new()
{

    private NeverNull(T reference)
    {
        _reference = reference;
    }

    private T _reference;

    public T Reference
    {
        get
        {
            if(_reference == null)
            {
                _reference = new T();
            }
            return _reference;
        }
        set
        {
            _reference = value;
        }
    }

    public static implicit operator NeverNull<T>(T reference)
    {
        return new NeverNull<T>(reference);
    }

    public static implicit operator T(NeverNull<T> value)
    {
        return value.Reference;
    }
}
like image 443
smartcaveman Avatar asked Dec 09 '10 15:12

smartcaveman


People also ask

When %s is used in C?

%s tells printf that the corresponding argument is to be treated as a string (in C terms, a 0-terminated sequence of char ); the type of the corresponding argument must be char * . %d tells printf that the corresponding argument is to be treated as an integer value; the type of the corresponding argument must be int .

Where do we use semicolon in C?

Role of Semicolon in C: Semicolons are end statements in C. The Semicolon tells that the current statement has been terminated and other statements following are new statements.

Where is all C used?

C is a powerful general-purpose programming language. It can be used to develop software like operating systems, databases, compilers, and so on. C programming is an excellent language to learn to program for beginners.

Why parenthesis is used in C?

It is used to control the order of operations in an expression. Example: (2 + 3) * 4, is 20 because parentheses have higher precedence than *. but 2 + 3 * 4 is 14, because * have higher precedence than +. (2) Generally parentheses are used to indicate a library functions and user defined function in C.


2 Answers

Well, one nasty thing is that this doesn't behave as you might expect it to naively:

NeverNull<Foo> wrapper1 = new NeverNull<Foo>();
NeverNull<Foo> wrapper2 = wrapper1;

Foo foo1 = wrapper1;
Foo foo2 = wrapper2;

That's going to create two instances of Foo because the original version was copied, before wrapper1 created an instance.

Basically, you're dealing with a mutable struct - which is almost never a nice thing to have. Additionally, I'm not generally keen on implicit conversions.

It feels like you're trying to achieve magic-looking code here... and I'm generally against that sort of thing. Maybe it makes sense for your particular use case, but I can't think of where I'd personally want to use it.

like image 117
Jon Skeet Avatar answered Oct 18 '22 19:10

Jon Skeet


As Jon correctly points out, the problem here is that the behaviour of the type is unexpected, not that it is slow. From a performance perspective, the overhead of the struct wrapper around the reference should be very low.

If what you want to do is to represent a non-nullable reference type then a struct is a reasonable way to do it; however, I would be inclined to make the struct immutable by losing the "auto creation" feature:

public struct NeverNull<T> where T: class 
{ 
    private NeverNull(T reference) : this()
    { 
        if (reference == null) throw new Exception(); // Choose the right exception
        this.Reference = reference; 
    } 

    public T Reference { get; private set; }

    public static implicit operator NeverNull<T>(T reference) 
    { 
        return new NeverNull<T>(reference); 
    } 

    public static implicit operator T(NeverNull<T> value) 
    { 
        return value.Reference; 
    } 
}

Make the caller responsible for providing a valid reference; if they want to "new" one up, let 'em.

Note also that generic conversion operators can give you unexpected results. You should read the spec on conversion operators and understand it thoroughly. For example, you cannot make a non-null wrapper around "object" and then have that thing implicitly convert to the unwrapping conversion; every implicit conversion to object will be a boxing conversion on the struct. You cannot "replace" a built-in conversion of the C# language.

like image 29
Eric Lippert Avatar answered Oct 18 '22 18:10

Eric Lippert