C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

ジェネリックメソッドとCS1503エラー

[トピック内 6 記事 (1 - 6 表示)]  << 0 >>

■96452 / inTopicNo.1)  ジェネリックメソッドとCS1503エラー
  
□投稿者/ 紅 (1回)-(2020/11/25(Wed) 09:28:03)

分類:[.NET 全般] 

開発環境;VS2019、.NET5、WPF

お世話になっております。
SaveImageFileというメソッドを作り、WPFで画像を保存する機能を作りました。
今後マウスでドラッグした内容も保存したいと考え、保存の変更点は引数を変えるだけでよかったので――
初めてジェネリックのType機能を使いました。
試しにジェネリックメソッドに変更したら、CS1503のエラーが出ました。
CS1503	引数 1: は Typeから System.IO.Streamへ変換することはできません
こういった使い方はできないのでしょうか?
以上よろしくお願いします。

■サンプル

private void Window_Closed(object sender, EventArgs e)
{
            // imgViewはWPFのImageコントロール名です。
            SaveImageFile<BitmapSource>(ファイルパス, (BitmapSource)imgView.Source);
            // 将来はIf文で分岐させ、下のコードを追加予定
            // SaveImageFile<CroppedBitmap>(ファイルパス, CroppedBitmapの変数);
}

private void SaveImageFile<Type>(string path, Type bmp)
{
            // 拡張子
            string ext = System.IO.Path.GetExtension(path).ToLower();
            using (Stream stream = new FileStream(path, FileMode.Create))
            {
                if (ext.EndsWith(".jpeg") || ext.EndsWith(".jpg"))
                {
                    var encoder = new JpegBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(bmp));
                    encoder.Save(stream);
                }
                else if (ext.EndsWith(".png"))
                {
                    var encoder = new PngBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(bmp));
                    encoder.Save(stream);
                }
                else if (ext.EndsWith("gif"))
                {
                    var encoder = new GifBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(bmp));
                    encoder.Save(stream);
                }
                else if (ext.EndsWith(".bmp"))
                {
                    var encoder = new BmpBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(bmp));
                    encoder.Save(stream);
                }
                else
                {
                    MessageBox.Show(
                        "対応していない拡張子です。画像の保存に失敗しました。",
                        "エラー",
                        MessageBoxButton.OK,
                        MessageBoxImage.Error);
                }
            }
}

引用返信 編集キー/
■96454 / inTopicNo.2)  Re[1]: ジェネリックメソッドとCS1503エラー
□投稿者/ とっちゃん (705回)-(2020/11/25(Wed) 11:00:29)
No96452 (紅 さん) に返信
> 開発環境;VS2019、.NET5、WPF
>
> お世話になっております。
> SaveImageFileというメソッドを作り、WPFで画像を保存する機能を作りました。
> 今後マウスでドラッグした内容も保存したいと考え、保存の変更点は引数を変えるだけでよかったので――
> 初めてジェネリックのType機能を使いました。
> 試しにジェネリックメソッドに変更したら、CS1503のエラーが出ました。
> CS1503 引数 1: は Typeから System.IO.Streamへ変換することはできません
> こういった使い方はできないのでしょうか?
> 以上よろしくお願いします。
>

今回の例に限って言えば、CroppedBitmap は、BitmapSource の派生クラスなので
Genericを使う必要はなく、そのまま BitmapSource クラスを引数として受け取るようにすれば解決します。

むしろ、BitmapEncoder のそっくりなコードを一つにまとめるという方向を検討することをお勧めします。
例えば、ext.EndWith( 拡張子 ) としていますが、もっとすっきり効率よくできないか?とか…
Path.GetExtension()で取得した文字列はどんな文字列なのか?とか…

あと、encoder を確保した後のコードが全く同じですよね?そこを一つにできないか?とか…



さて、本題。

エラーが出てるか所ですが、各エンコーダの以下の行であってますか?
> encoder.Frames.Add(BitmapFrame.Create(bmp));

こちらは、Type がGenericとして参照されているのではなく、
Type という「型」で受け取ってしまうことで発生していると思います。

Generic で定義する場合、<> の中に来るのは、型名やクラスメンバー名として存在していないものを指定する必要があります。
今回は、Type がそのまま Type という class に当てはまってしまうため、結果としてGenericになっておらず
Type から、BitmapFrame.Create() メソッドが引数として受け入れようとする何かに当てはめられずにエラーとなる
という状況だと思います。

Type の代わりに、TYPE(全部大文字あるいはその逆に小文字でもよい)や、T など、当たらないものを指定すれば
解決すると思います。

引用返信 編集キー/
■96455 / inTopicNo.3)  Re[1]: ジェネリックメソッドとCS1503エラー
□投稿者/ 魔界の仮面弁士 (2923回)-(2020/11/25(Wed) 11:14:17)
2020/11/25(Wed) 13:58:50 編集(投稿者)

No96452 (紅 さん) に返信
> else if (ext.EndsWith(".png"))
EndsWith は不要なのでは?
仮に二重拡張子だったとしても、ext には 末尾の .png のみが保持されているはずですよね。


> こういった使い方はできないのでしょうか?
今回の場合、BitmapFrame.Create(bmp) として渡そうとしているわけですから、
bmp のデータ型が、以下のいずれか(あるいはその派生クラス)でないとエラーになります。
 1) System.IO.Stream クラス
 2) System.Uri クラス
 3) System.Windows.Media.Imaging.BitmapSource クラス

これらの型には互換性が無いため、ジェネリックの型制約については
where Type:class か where Type:notnull ぐらいしか付与できません。

今回の場合はジェネリックにするメリットが無さそうなので、
オーバーロードで解決させた方が良いと思いますよ。


#nullable enable
private void SaveImageFile(string path, ImageSource image)
{
  var bmp = image as BitmapSource ?? throw new ArgumentOutOfRangeException(nameof(image));
  SaveImageFile(path, bmp);

}
private void SaveImageFile(string path, BitmapSource bmp)
{

  BitmapFrame frame;
  switch (bmp)
  {
    case CroppedBitmap src: // CroppedBitmap の時に行いたい処理
      frame = BitmapFrame.Create(src);
      break;
    case BitmapSource:
    default:
      frame = BitmapFrame.Create(bmp);
      break;
  }
  string ext = System.IO.Path.GetExtension(path).ToLower();
  if (encoderFactory.TryGetValue(ext, out var factory))
  {
    using (var stream = new FileStream(path, FileMode.Create))
    {
      var encoder = factory();
      encoder.Frames.Add(frame);
      encoder.Save(stream);
    }
  }
  else
  {
    // throw new ArgumentOutOfRangeException(nameof(bmp), "対応していない拡張子です。");
    MessageBox.Show(
      "対応していない拡張子です。画像の保存に失敗しました。",
      "エラー",
      MessageBoxButton.OK,
      MessageBoxImage.Error);
  }
}
private static readonly Dictionary<string, Func<BitmapEncoder>> encoderFactory = new()
{
  [".jpeg"]= () => new JpegBitmapEncoder(),
  [".jpg"] = () => new JpegBitmapEncoder(),
  [".png"] = () => new PngBitmapEncoder(),
  [".gif"] = () => new GifBitmapEncoder(),
  [".bmp"] = () => new BmpBitmapEncoder(),
};
引用返信 編集キー/
■96457 / inTopicNo.4)  Re[2]: ジェネリックメソッドとCS1503エラー
□投稿者/ 紅 (2回)-(2020/11/25(Wed) 13:54:40)
とっちゃんさん、魔界の仮面弁士さん、ご回答ありがとうございました。
コードのご指摘や考え方を含め感謝しております。
いろいろと課題が見つかったので、少しずつ消化していきます。
ご提示頂いたコードに差し替え、一先ずデバッグで動きを把握することから始めます。
(エラーの個所はbmp変数が含まれる4か所でした)
ジェネリックではなくオーバーロードを用いた解決法など、いろいろと勉強になりました。
EndsWithで比較する癖がついてたのですが――
ご提示いただいたencoderFactoryとTryGetValueの組み合わせを参考に改善します。
解決済み
引用返信 編集キー/
■96458 / inTopicNo.5)  Re[3]: ジェネリックメソッドとCS1503エラー
□投稿者/ ぶなっぷ (260回)-(2020/11/26(Thu) 10:28:31)
2020/11/26(Thu) 10:55:39 編集(投稿者)
参考までに、ジェネリックを使うべき時はどんなときか?
というお話です。

.Netにおいて使用される言語だと、似て非なる処理を1つにまとめるために、
ざっと思いつく限りで以下のような手段があります。

1) 条件分岐
2) オーバーライド
3) ジェネリック

データ値の違いで処理分けを行い、それ以外は共通にしたい場合が、
「条件分岐」です。
--------------------------------------------------------------------
  if(条件1) { 条件1のとき行う処理(); }
  共通処理();
--------------------------------------------------------------------

データ型の違いで処理分けを行い、それ以外は共通にしたい場合が、
「オーバーライド」です
--------------------------------------------------------------------
  public class Base
  {
    public virtual void Func() { 共通処理(); }
  }

  public class Deriv1 : Base
  {
    public override void Func()
    {
      base.Func();
      Deriv1クラスのとき行う処理();
    }
  }

  public class Deriv2 : Base
  {
    public override void Func()
    {
      base.Func();
      Deriv2クラスのとき行う処理();
    }
  }
--------------------------------------------------------------------

そして、処理分けするのではなく、データ型の違いそのものを意識せず、
基本的に全てを共通処理としたい場合が、「ジェネリック」です。
--------------------------------------------------------------------
  public T Max<T>(T a, T b)
    where T : IComparable
  {
    return (a.CompareTo(b) > 0) ? a : b;
  }
--------------------------------------------------------------------
上記のようにすれば、Tの型が何であれ、IComparableさえ実装していれば、
大きいほうを返します。
(例) トレースしてみる
  Trace.WriteLine(Max(2, 5));
  Trace.WriteLine(Max(3.14, 2.72));
  Trace.WriteLine(Max("abc", "xyz"));
  Trace.WriteLine(Max(new DateTime(2020, 11, 26), new DateTime(2021, 1, 1)));
(例) トレース結果
  5
  3.14
  xyz
  2021/01/01 0:00:00

なんとなく伝われば幸いです

解決済み
引用返信 編集キー/
■96473 / inTopicNo.6)  Re[4]: ジェネリックメソッドとCS1503エラー
□投稿者/ 紅 (4回)-(2020/11/27(Fri) 15:09:59)
ぶなっぷさんご回答ありがとうございます。
以前に読んだC++の書籍でテンプレートの機能があったのを思い出し、C#でもないかなと軽い気持ちで
調べ使ったのが間違った用法に繋がりました。
ジェネリッククラスはWhere句で指定したインターフェースが肝だとわかりました。

>追伸
オーバーロードなどは言葉で聞いた程度の認識だったので、今回の質問は非常に参考になりました。
Funcとか数える程度しか使ってなかったので、DictionaryのValueで指定する使い方は目から鱗でした。
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ