web-dev-qa-db-ja.com

2つのパラメーターを持つデリゲートをパラメーター関数として渡す

非常によく似た一連の関数がありますが、次の2つのように1行です(ただし、さらに多くの関数があります)。

private static int HowManyHoursInTheFirstYear(IList<T> samples)
{
    DateTime firstDate = samples[0].Date;
    int count = 0;

    while (count < samples.Count && 
          samples[count].Date.Year == firstDate.Year)
    {
        count++;
    }

    return count;
}


private static int HowManyDaysInTheFirstMonth(IList<T> samples)
{
    DateTime firstDate = samples[0].Date;
    int count = 0;

    while (count < samples.Count && 
           samples[count].Date.Month == firstDate.Month) // <--- only change!
        count++;
    }

    return count;
}

デリゲートを使用して、コード内のこの繰り返しをエレガントな方法で削除することを考えていました。これにより、次のような呼び出しが可能になります。

HowManyDaysInTheFirstPeriod(
    samples,
    delegate(DateTime d1, DateTime d2) { return d1.Month == d2.Month; });

これにより、次のようにデリゲートを宣言します。

delegate bool DateComparer(DateTime first, DateTime second);

そして、HowManyDaysInTheFirstPeriodは次のようなものになります。

private static int HowManySamplesInFirstPeriod
    IList<T> samples,
    DateComparer comparer)
{
    DateTime firstDate = samples[0].Date;
    int count = 0;

    while (count < samples.Count && comparer())
    {
        count++;
    }
}

残念ながら、コンパイラーは、compareが2つのパラメーターを必要とすることを報告します。

私はC#に比較的慣れていないため、ここで障害となります。これをどのように解決しますか?

23

あと少しです! comparerデリゲートパラメーターは他の関数と同じです:それを呼び出すには、適切な引数を渡す必要があります。あなたの場合、これは、この変更を意味します。

while (count < samples.Count && comparer(samples[count].Date, firstDate))
{
    count++;
}

(また、samplesはおそらくsamples.Count、上で書いたように。)

14
John Feminella

少し簡単にできます。 DateTimeから比較する必要があるものを抽出するデリゲートを関数に提供するだけです。

private static int HowManySamplesInFirstPeriod<T>
    IList<T> samples,
    Func<DateTime, int> f // a function which takes a DateTime, and returns some number
{
    DateTime firstDate = samples[0].Date;
    int count = 0;

    while (count < samples && f(samples[count].Date) == f(firstDate))
    {
        count++;
    }
}

そしてそれはそのように呼び出すことができます:

HowManySamplesInFirstPeriod(samples, (dt) => dt.Year); // to get the year
HowManySamplesInFirstPeriod(samples, (dt) => dt.Month); // to get the month

(dt) => dt.year構文は新しいかもしれませんが、「何らかのジェネリック型のオブジェクトdtを受け取り、dt.yearを返す匿名デリゲート」を書くよりきれいな方法です。代わりに、昔ながらのデリゲートを作成することもできますが、これはより優れています。 :)

ただし、もう1つのジェネリック型パラメーターを追加することで、それを少し一般的にすることができます。

private static int HowManySamplesInFirstPeriod<T, U>
    IList<T> samples,
    Func<DateTime, U> f // Let's generalize it a bit, since the function may return something other than int (some of the DateTime members return doubles, as I recall)

いつものように、LINQはより良い代替手段を提供します:

private static int HowManySamplesInFirstPeriod<T>
    IList<T> samples,
    Func<DateTime, int> f)
{
  var firstVal = f(samples.First().Date);
  return samples.Count(dt => f(dt.Date) = firstVal)
}
6
jalf

問題の2つの日付を比較演算子に渡す必要があります。おそらく次のように単純です。

private static int HowManySamplesInFirstPeriod
    IList<T> samples,
    DateComparer comparer)
{
    DateTime firstDate = samples[0].Date;
    int count = 0;

    while (count < samples.Count 
           && comparer(samples[count].Date, firstDate))
    {
        count++;
    }
}

または、逆に渡すこともできます(つまり、firstDate、次にsamples[count].Date)。

4
Jon Skeet

Jalfの答えは、元の使用法に合わせて少し変更する必要があると思います。

private static int HowManyHoursInTheFirstYear(IList<DateTime> samples, Func<DateTime, DateTime, bool> comparer) 
{ 
    DateTime firstDate = samples[0].Date; 
    int count = 0;
    while (count < samples.Count && comparer(samples[count], firstDate) ) {
        count++;
    } 
    return count; 
}

次を使用して呼び出す:

HowManyDaysInTheFirstPeriod(samples, (d1, d2) = > { return d1.Month == d2.Month; });
HowManyDaysInTheFirstPeriod(samples, (d1, d2) = > { return d1.Year == d2.Year; });
1
sipwiz

デリゲートと比較される日付を渡す必要があります。そう:

comparer(samples[count].Date, firstDate)
0
Kent Boogaart