1

This is pure curiosity/challenge, no practical importance at all. So I'm not looking for alternate solutions that get the job done.

From this question Most efficient way to check for DBNull and then assign to a variable? I found this answer which looks like:

oSomeObject.IntMemeber = oRow["Value"] as int? ?? iDefault;
oSomeObject.StringMember = oRow["Name"] as string ?? sDefault;

Can I move the above expressions to one generic function (or two, or more) so that it accepts both int? and string as well and that I can call it like:

oSomeObject.IntMemeber = oRow.Read<int?>("Value", 0); //iDefault is now 0
//or
oSomeObject.IntMemeber = oRow.Read<int>("Value"); //iDefault is now default(int)

//and 
oSomeObject.StringMember = oRow.Read<string>("Name"); //sDefault is now default(string)

Requirements:

1) I need an option to specify a default value in case of DBNulls. I also need an option to return default(T) in case I dont specify a default value. So this wont work:

public static T Read<T>(this IDataRecord dr, string field, T defaultValue) where T : class
{
    return dr[field] as T ?? defaultValue;
}

public static T? Read<T>(this IDataRecord dr, string field, T? defaultValue) where T : struct
{
    return dr[field] as T? ?? defaultValue;
}

because I cant call oSomeObject.StringMemeber = oRow.Read<string>("Name")

It need not be optional parameter, it can even be an overload:

public static T Read<T>(this IDataRecord dr, string field) where T : class
{
    return dr[field] as T ?? default(T);
}

public static T? Read<T>(this IDataRecord dr, string field) where T : struct
{
    return dr[field] as T? ?? default(T?);
}

public static T Read<T>(this IDataRecord dr, string field, T defaultValue) where T : class
{
    return dr[field] as T ?? defaultValue;
}

public static T? Read<T>(this IDataRecord dr, string field, T? defaultValue) where T : struct
{
    return dr[field] as T? ?? defaultValue;
}

This wont compile, since method 1 and 2 have just the same signature.

2) Generic functions (in case of overloads) should have the same name.

3) as keyword must be used check and cast type. As I stated previously I'm not really looking to solutions to read from IDataRecord or enhance performance or something.


There are similar questions

  1. C# generic class using reference types and nullable value types

  2. Is creating a C# generic method that accepts (nullable) value type and reference type possible?

  3. Can a Generic Method handle both Reference and Nullable Value types?

But this is very specific to as keyword. So the answers there are not applicable.

Addendum: I know there won't be one single solution to this. I will accept which is most elegant alternative.

Community
  • 1
  • 1
nawfal
  • 70,104
  • 56
  • 326
  • 368
  • "Generic" mean that the same IL can be used for *all* types that match the constraints. The problem is that reference types and nullable value types require different IL for the same operator (`as`). So you need different methods. And methods with the same name must differ in their parameter lists. So unless you're looking for a solution which, for example, compiles some IL for each type at runtime, I'd say that it won't get more elegant than what you already have. – dtb Feb 10 '13 at 08:58
  • hmm, but sadly I don't already have a solution. – nawfal Feb 10 '13 at 09:01
  • 1
    Use different names for your methods? – dtb Feb 10 '13 at 09:02
  • Yes thats always possible. Seems thats the most elegant solution :) – nawfal Feb 10 '13 at 09:02

3 Answers3

0

The answers you linked to seem to pretty clearly indicate that there isn't a good way to have a generic method handle both reference types and value types in the clean way you would like. It seems the answer, as shown there, is to give them different names.

You can, however, reduce your code to just two methods instead of four by using a default value for the defaultValue parameter:

public static T Read<T>(this IDataRecord dr, string field, 
                        T defaultValue = null) where T : class
{
    return dr[field] as T ?? defaultValue;
}

public static T? ReadNullable<T>(this IDataRecord dr, string field, 
                                 T? defaultValue = null) where T : struct
{
    return dr[field] as T? ?? defaultValue;
}

This way you can use dr.ReadNullable(field) when you want the result to be null when the value can't be cast, and dr.ReadNullable(field, someValue) when you want to specify a default value.

As discussed below, the use of default(T) and default(T?) in your original code aren't necessary, since they will always be null anyway.

JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • `default(T?)` is `null`. Do you mean `default(T)`? – dtb Feb 10 '13 at 09:04
  • `default(T?)` is better isn't it? – nawfal Feb 10 '13 at 09:04
  • 1
    `x ?? null` is the same as `x` -- why check if `x` is `null` if you return `null` in that case anyway? – dtb Feb 10 '13 at 09:06
  • 1
    @dtb I was sticking as close to nawfal's original code as possible to just answer the question at hand, but it did strike me as a little odd. Nawfal, is the intention just to return a null value when the cast fails? – JLRishe Feb 10 '13 at 09:07
  • @JLRishe let it be anything, I can always change it from the caller side. Anyway thats not the larger problem at hand right.. – nawfal Feb 10 '13 at 09:11
  • @JLRishe whats the point of `(defaultValue ?? default(T?))`. Also as dtb points there is no need of `default(T?)` or `default(T)` in your first example. Can I edit it? – nawfal Feb 10 '13 at 09:16
  • @nawfal The point of that is to use `defaultValue` if it is specified, and `default(T?)` if it is not. But as we have been discussing, the `default(T?)` is meaningless. I will edit the post. – JLRishe Feb 10 '13 at 09:19
  • @nawfal I just noticed this comment in your original question: "//iDefault is now default(int)". So was the intention for the result to be `default(int)` (which is `0`) or `default(int?)` (which is `null`) in that situation? The code you supplied would have returned `default(int?)`. – JLRishe Feb 10 '13 at 09:28
  • @JLRishe that was a hypothetical example as I wouldnt know what solution SO-users would come up with. Just saying any such solution is welcome. For eg, if i do `row.Read("")` since I am not specifying any default value, its ok to give back a 0 (I was assuming what if the method overload is not treating my `int` as `int?`) Its also ok if someone writes a method to treat `int` as `int?` and return a null back. More than any implementation or return result it was a question about overload resolution – nawfal Feb 10 '13 at 09:34
  • 1
    I see. The general concensus seems that .NET can't resolve overloads based solely on generic type constraints. – JLRishe Feb 10 '13 at 09:40
  • @JLRishe see my answer, do you find it anywhere near as good as yours? :P – nawfal Feb 10 '13 at 09:54
  • 'there isn't a good way to have a generic method handle both reference types and value types in the clean way you would like'. Actually you are not right, please check my answer. – SergeyS Feb 10 '13 at 10:15
  • @SergeyS unfortunately JLRishe, dtb and the ones that viewed the question understood the "purport" of the question and answered accordingly. – nawfal Feb 10 '13 at 10:18
  • @nawfal I guess which approach to use depends on what is more inconvenient in your situation - specifying a default value, or calling with different names. One issue with the way you're demonstrating using your approach is that this: `s.UserId = (int)r.Read("");` will cause an `InvalidCastException` if the item is not found. I've posted an alternate suggestion based on your approach that should avoid this issue and save you from having to do that `(int)` cast at all. – JLRishe Feb 10 '13 at 10:21
  • @JLRishe yes, but I dont think thats the right approach. The power to choose what to return should be on caller side. I know there is no Null values in table so I used a cast accordingly. If there were I wouldn't have, and passed `0` as default value if I wanted. What if I want to return `null` and my `UserId` field is `int?` ? – nawfal Feb 10 '13 at 10:27
  • @nawfal That's a good point. There's no way to get an `int?` out of either of the methods in my second approach. I'll remove it. – JLRishe Feb 10 '13 at 10:36
0

Seems there is not one way. JLRishe's answer is good. It relies on renaming overloads. Here is something that doesn't rename functions, but compromise on ease of calling (ie one optional parameter less)

static T Read<T>(this object obj, T defaultValue) where T : class
{
    return obj as T ?? defaultValue;
}

static T? Read<T>(this object obj, T? defaultValue) where T : struct
{
    return obj as T? ?? defaultValue;
}

public static T Read<T>(this IDataRecord dr, string field, 
                        T defaultValue) where T : class //no optional parameter here :(
{
    return dr[index].Read<T>(defaultValue);
}

public static T? Read<T>(this IDataRecord dr, string field, T? defaultValue = null) 
                        where T : struct
{
    return dr[index].Read<T>(defaultValue);
}

And call, for eg.:

Session s = new Session();
s.Id = r.Read("", (byte[])null);
s.UserId = (int)r.Read<int>("");
s.LoginTime = (DateTime)r.Read<DateTime>("");
s.LogoutTime = r.Read("", default(DateTime?));
s.MachineFingerprint = r.Read("", (string)null);

As you see reference types needs to be specified a default value. Not so elegant..

nawfal
  • 70,104
  • 56
  • 326
  • 368
0

Idea is to use is instead of as. Below methods will do the trick:

    public static T Read<T>(this IDictionary<string, object> dr, string field, T defaultValue)
    {
        var v = dr[field];
        return v is T ? (T)v : defaultValue;
    }

    public static T Read<T>(this IDictionary<string, object> dr, string field)
    {
        var v = dr[field];
        return v is T ? (T)v : default(T);
    }

Usage:

    Dictionary<string, object> d = new Dictionary<string, object>();

    d["s"] = "string";
    d["i"] = 5;
    d["db.null"] = DBNull.Value;

    Console.WriteLine(d.Read("i", 7));                        // 5
    Console.WriteLine(d.Read("s", "default string"));         // string
    Console.WriteLine(d.Read("db.null", "default string"));   // default string
    Console.WriteLine(d.Read("db.null", -1));                 // -1
    Console.WriteLine(d.Read<int>("i"));                      // 5
    Console.WriteLine(d.Read<string>("s"));                   // string
    Console.WriteLine(d.Read<int>("db.null"));                // 0
    Console.WriteLine(d.Read<string>("db.null") ?? "null");   // null

I have used Dictionary for quick examples, IDataRecord will behave the same way.

SergeyS
  • 3,515
  • 18
  • 27
  • that's not my question precisely. – nawfal Feb 10 '13 at 10:00
  • @nawfal Why this is not your question? Which requirement it does not meet? – SergeyS Feb 10 '13 at 10:00
  • the third one. Not only you're reading from the reader twice, you're checking for a cast twice. `as` avoids that. I know these arent expensive, but that was not my question. Even in the link from which i copied the test sample has plenty of solutions that use `is`, including one my own. If you're really a fan of optimizations you should see that link and avoid the one you posted :) – nawfal Feb 10 '13 at 10:03
  • @nawfal The specification is clear on this point; as (in the non-dynamic case) is defined as a syntactic sugar for is. Check this article http://blogs.msdn.com/b/ericlippert/archive/2010/09/16/is-is-as-or-is-as-is.aspx – SergeyS Feb 10 '13 at 10:08
  • @nawfal And I updated code to help you understand that accessing reader twice is not required here :) – SergeyS Feb 10 '13 at 10:11
  • one, its not a specification, its eric's take. two, you should read further, I quote "However, in practice the CLR provides us instruction isinst, which ironically acts like as. Therefore we have an instruction which implements the *semantics of as pretty well, **from which we can build an implementation of is***". three, ignore the sugar theory, in practice do they do the same? no. four, whatever they are, the link I posted helps with `is` cases, I specifically asked about `as`. – nawfal Feb 10 '13 at 10:16
  • I am not very sure about that. Do you have some reference or `IL`? – nawfal Feb 10 '13 at 10:30
  • @nawfal - I checked IL, actually they both use instruction `isinst` – SergeyS Feb 10 '13 at 10:53
  • They both using `isinst` shouldnt alone prove it. Do they both produce the exact same IL? I am not sure. may be in release builds. – nawfal Feb 10 '13 at 11:11
  • @nawfal - No, they do not produce exact code. But i'm just not sure why you want to use `as` . If you think it will give you performance boost, you are optimizing wrong things. – SergeyS Feb 10 '13 at 11:20
  • Thats the first line of my question. Just curiosity, learning. Not only your solution might perform better, its much easier to call from caller side. I was just exploring the other side. I thought my question made it clear. I mentioned its not a performance question or so. You may edit my question if it was not for u. – nawfal Feb 10 '13 at 11:22