この種の行動を行うジェネリックスの方法がありますか?

DataRow からたくさんのパッキングとアンパックを行います。はい、私たちはORMを使うべきですが、それまではこれが私たちのものです。この結果、次のようなコードがたくさんあります:

string username;

var temp = dr["Username"];
if (DbNull.Equals (temp))
{
    username = "Anonymous";
} else {
    username = dr["Username"].ToString();
}

最終的に、これはパターンになり、ヘルパーメソッドに変換されました。

string username = StringExtensions.SafeParse (dr["Username"], "Anonymous");

This is still cumbersome, and required extension methods for all kinds of primitives. It also clutters up the code. I created a generic extension method, on object, called As. Usage looks like:

string username = dr["Username"].As ("Anonymous");

この比較的単純な変更は、他の開発者との大きな寛大さを満たし、多くの場所で使用されています。私が不満を感じているのは、潜在的なパフォーマンスの影響です。 今は時期尚早の最適化がないことを認識しています。早すぎる最適化を行わずにコードを書いたことは間違いありません。後でそれを最適化することは大したことではありません。私は比較的控えめな2GHzワークステーションで毎秒250万回のコンバージョンを行う方法をベンチマークしました。これは他の開発者を保存する時間と読みやすさの向上に比べて驚異的なパフォーマンスです。しかし、以下のコードのサンプルを見ると、私は言語の機能を誤っているように感じ、もっとうまくいくかもしれません。このメソッドはxmldocされています。 "大声で叫ぶ"と大声で叫びます!私はダブルボクシングを避けるためのより良い方法を探しています。実際のバージョンは簡潔にするために省略してありますが、実際には多くの場合 TryParse が使用されています。

public static TDestination As<tdestination> (this object source, TDestination defaultValue = default(TDestination))
{
    if (source is TDestination)
        return (TDestination) source;

    if (source == null || DbNull.Equals(source))
        return defaultValue;

    if (TDestination is int)
        return (TDestination) (object) Convert.ToInt32 (source.ToString ());

    if (TDestination is long)
        return (TDestination) (object) Convert.ToInt64 (source.ToString ());

    if (TDestination is short)
        return (TDestination) (object) Convert.ToInt16 (source.ToString ());

   //and so on...
}
3
あなたの As <tdestination> コードサンプルはコンパイルされません。
追加された 著者 phoog,

5 答え

あなたのオブジェクトがIConvertibleかどうかをチェックし、そうであればToTypeを使う:

var convertible = source as IConvertible;
if (convertible != null)
    return (TDestination)convertible.ToType(typeof(TDestination), Thread.CurrentThread.CurrentUICulture);
3
追加された
私はこれが好きです。
追加された 著者 Bryan Boettcher,

あなたの質問で与えられた例の As メソッドに基づいて、代わりにこれを行うことができます:

public static TDestination As<tdestination>
    (this object source, TDestination defaultValue = default(TDestination))
{
    if ((source == null) || Convert.IsDBNull(source))
        return defaultValue;

    return (TDestination)source;
}
3
追加された
@insta:いいえ、そうではありません。しかし、 Convert.ToInt32 を呼び出す前に独自の As メソッドでif(source int)チェックを行い、 if(source source がボックス化された int であることを既に知っている場合は、 Convert.ToInt64 Convert.ToInt32 のあとに object のボクシングキャストを続ける ToString /code> - TDestination にキャストします。 (同様に longshort などと同様)なぜ無意味なラウンドトリップを避け、直接 TDestination にキャストするのはなぜですか?
追加された 著者 LukeH,
そのようなストレートキャストは変換動作を行いますか、それとも暗黙のキャストのみに依存していますか?
追加された 著者 Bryan Boettcher,
ああ、私は質問をねじ込んだので、それはキリストです。私のコードは実際にはゲームを少し変更するソースではなく、typeof(TDestination)のチェ​​ックを行います。
追加された 著者 Bryan Boettcher,

Whenever i goes into reflection or checking the T of my generic class i'm going to use a dictionary Dictionary. As value i always put something in that should be done for each time as Func or Action. In your case i would write it maybe in that way:

public static class MyConverter
{
    private static Dictionary> _MyConverter;

    static MyConverter()
    {
        _MyConverter = new Dictionary>(); //Use the Add() method to include a lambda with the proper signature. _MyConverter.Add(typeof(int), (source) => Convert.ToInt32 (source.ToString())); //Use the index operator to include a lambda with the proper signature. _MyConverter[typeof(double)] = (source) => Convert.ToDouble(source.ToString()); //Use the Add() method to include a more complex lambda using curly braces. _MyConverter.Add(typeof(decimal), (source) => { return Convert.ToDecimal(source.ToString()); }); //Use the index operator to include a function with the proper signature. _MyConverter[typeof(float)] = MySpecialConverterFunctionForFloat; } //A function that does some more complex conversion which is simply unreadable as lambda. private static object MySpecialConverterFunctionForFloat(object source) { var something = source as float?; if (something != null && something.HasValue) { return something.Value; } return 0; } public static TDestination As<tdestination>(this object source, TDestination defaultValue = default(TDestination)) { //Do some parameter checking (if needed). if (source == null) throw new ArgumentNullException("source"); //The fast-path exit. if (source.GetType().IsAssignableFrom(typeof(TDestination))) return (TDestination)source; Func func; //Check if a converter is available. if (_MyConverter.TryGetValue(typeof(TDestination), out func)) { //Call it and return the result. return (TDestination)func(source); } //Nothing found, so return the wished default. return defaultValue; } } 

このアプローチの唯一の欠点は、 object の使い方がボクシングにつながるということです。ボクシングは非常に短時間で頻繁に繰り返し呼び出されるとパフォーマンス上の問題を引き起こす可能性があります。しかし、常に請求前に測定しています。

反対側では、それ以上のコンバーターを追加するのは簡単ですし、辞書の使用により常にO(1)になります。

2
追加された
これは素晴らしいですね。 Visual Studioにアクセスしてテストしていますか? TDestination についてはわからないので、コンストラクタに問題が見つかりました。辞書の初期化を As メソッド自体に移動すると、キャッシュの目的を無効にするようです。
追加された 著者 Bryan Boettcher,

Datarow Fieldプロパティの拡張メソッドを定義する方法は、フィールドと同じ型の独自のnull値を指定できます。次のようにします。

        public static T Field(this DataRow row, string columnName, T nullValue) { return !row.IsNull(columnName) ? row.Field(columnName) : nullValue; }
0
追加された

Field関数のバリエーションを作るのが最善の方法だと私は同意しますが、パフォーマンスについて懸念がある場合は、IsNull()または実際のField関数を使用しないでください。以下の方法はあなたが本当に必要なものです。

public static T Field(this DataRow row, string columnName, T nullValue)
{
  object value = row[columnName];
  return ((DBNull.Value == value) ? nullValue : (T)value);
}

これにより、追加ボクシングの必要性がなくなります。また、nullValueパラメータの使い方に注意している場合は、関数を呼び出すときにTを明示的に指定する必要がありません。 win-win。

0
追加された
拡張メソッドはDataRowに固有のものではありませんが、これはその最も一般的な使い方です。また、汎用の鋳造およびデータ変換方法としても使用しています。
追加された 著者 Bryan Boettcher,
その場合、前述の静的コンストラクタメソッドが最も柔軟なソリューションです。しかしそれはかなりの価格で来る。パフォーマンスに本当に関心がある場合は、私があなたの最も一般的なケースで提供したような特殊なAS関数を作成することを検討する必要があります。こうすることで、できるだけ最適化することができます。
追加された 著者 0rigin,