xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

サクッと簡単!Excel → PDF 変換

会社で今日こんなお願いをされました。

このフォルダ以下にある全部の Excel ファイルを PDF にしてほしい!ファイル数メッチャあるからチマチマやってられーん!Help !!

ということでサクッとツールを作ってあげたわけですが、案外便利なのではないかと思ったので載せておきます。

f:id:xin9le:20160721220738p:plain

Excel → PDF 変換

Office PIA (相互運用アセンブリ) を使って変換するためのエッセンスは以下のような感じです。Workbook.ExportAsFixedFormat メソッドを使いましょうPDF だけでなく XPS 形式での出力も可能です。

var sourcePath = @"C:\Temp\sample.xlsx";
var targetPath = sourcePath.Replace(".xlsx", ".pdf");
var format = XlFixedFormatType.xlTypePDF;
var quality = XlFixedFormatQuality.xlQualityStandard;

var app = new Application();
var workbook = app.Workbooks.Open(sourcePath);  //---ブックを開いて
workbook.ExportAsFixedFormat(format , targetPath, quality);  //--- PDF形式で出力
app.Quit();

ソースコード

とりあえずでワンポイントで動けばいい系なので超絶雑ぃですが、全体像でもこれだけです。app.config に対象フォルダの指定を外出ししてあるので汎用的に利用できます。

using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Office.Interop.Excel;

namespace Excel2Pdf
{
    class Program
    {
        static void Main()
        {
            var rootPath = ConfigurationManager.AppSettings["SourceFolderPath"];
            var files = Directory.GetFiles(rootPath, "*.xlsx", SearchOption.AllDirectories);

            //--- 件数
            Console.WriteLine($"対象フォルダ : {rootPath}");
            Console.WriteLine($"ファイル数 : {files.Length}");

            //--- 開始確認
            Console.WriteLine("Please press any key to start.");
            Console.ReadLine();

            //--- 変換
            for (int i = 0; i < files.Length; i++)
            {
                var path = files[i];
                Console.WriteLine($"{i + 1}/{files.Length} : {path.Replace(rootPath, string.Empty)}");
                Convert(path);
                GC.Collect();
            }

            //--- おまじない的に3秒ほど待ってみる
            Thread.Sleep(3000);

            //--- Excel.exe が死ななかったら困るので、念のためプロセスを強制的に殺す処理を入れておく
            var excels = Process.GetProcessesByName("EXCEL");
            foreach (var x in excels)
                x.Kill();
        }

        static void Convert(string sourcePath)
        {
            Application app = null;
            Workbook workbook = null;
            try
            {
                //--- Excelファイルを開く
                app = new Application();
                workbook = app.Workbooks.Open(sourcePath);

                //--- PDFとして保存
                var targetPath = sourcePath.Replace(".xlsx", ".pdf");
                workbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, targetPath, XlFixedFormatQuality.xlQualityStandard);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"エラー : {ex.Message}");
            }
            finally
            {
                if (workbook != null)
                {
                    Marshal.ReleaseComObject(workbook);
                    workbook = null;
                }

                //--- Excelを終了
                app.Quit();
                Marshal.ReleaseComObject(app);
                app = null;
            }
        }
    }
}

こんなので人助けになって喜ばれるんだから、プログラミングはできて損しないですね!

C# 7 をライブコーディングで解説してきました vol.2

7/16 (土)、福井大学でソフトウェア技術者サミット in 福井 2016が開催され、そこに参加/登壇してきました。ハッシュタグは #sesfukui (Software Engineer Summit in Fukui)。

開催の運びとなったのは VSUG DAY - THE FINAL - の懇親会で僕の師匠である小島さん (@Fujiwo) が「同じぐらいの規模のヤツ、福井でもやろう!」と言ったことに始まります。小島さんの人脈と運営力のおかげで東京でも滅多に集まらないような素晴らしい方々が終結しました。そんな中、(登壇者の中では) 最年少のペーペーが偉そうに C# 7 の最新機能 (7/16 現在) について話してきました。

セッション資料

セッション資料は前回同様、ライブコーディングによるデモのおさらいという形で簡単にまとめてあります。PDF 形式で Docs.com に置いてあるので、ご自由にダウンロードなどしてください。

タイトルスライドのデザイン、今回は「福井の夏」ということで名物である三国花火を選んでみました。たぶん、みんな気付いてないと思うw

ざっくりと内容

簡単に言うと、C# ユーザー会 //build/ 2016 振り返り勉強会 でやった内容 + 追加された以下の機能の紹介でした。

機能は増えたけど説明する時間は全く増えていない & 余計なことしてペース配分をミスるというアレで、Type Switch についての説明ができなかったというガッカリぶり。とても反省しています...。次があるかは分からないけど、同じミスはしない所存!(キリッ

世界で最も新しいコンパイラ

当日会場に着いてから Roslyn の最新のコミットを pull してビルドしたものでデモしました。正真正銘世界最新の C# コンパイラというキャッチーな煽り文句を言いたかっただけなのですが、やっぱり何があっても as-is なワケで案の定デモ中に Visual Studio が落ちるというイタイ目に...。まぁそれも一興ということでw

他の方々の資料 / レポート

型分解 - タプルから変数への展開

7/12 (火) の早朝、長らく検討/開発されてきた待望の Deconstructions (= 型分解) が Roslyn の master ブランチにマージされました!(型分解というのは僕のテキトーな訳で、正式名称は未定)

タプル構文が複数の値をひとつにまとめる機能だったのに対し、Deconstructions はその逆の複数の値への分解をサポートします。C# 7 への搭載の期待もさることながら、直近では Visual Studio 15 Preview 4 にも含まれるのではないかと思われます。タプル構文については以下を参照ください。

f:id:xin9le:20160714055622p:plain

基本的な記法

まず、System.ValueTuple を分解しつつ基本の書き方を見ていきましょう。以下のような 3 種類の書き方があります。(もっとあったらゴメンナサイ...

//--- こんなタプル型のインスタンスがあったとする
var t = (123, "abc");

//--- Let's 分解
(int x, string y) = t;  //--- 型を明示
(var x, var y) = t;     //--- 一部 (or 全部) の型を推論
var (x, y) = t;         //--- すべてを型推論で解決

//--- それぞれの別々の変数として利用できる
Console.WriteLine($"({x}, {y})");  //--- (123, abc)

既存の変数への割り当て

先の例では変数を生成しつつ値を分解/割り当てしましたが、すでに宣言された既存の変数に対しても適用できます。この場合は型の指定は必要ありません

int x;
string y;
(x, y) = (123, "abc");  //--- ValueTuple を生成して即座に x, y に分解

配列要素やプロパティへの割り当て

変数にだけ代入できるわけではありません。もちろん配列の要素や setter のあるプロパティにも適用できます。

//--- 配列要素の入れる
var a = new int[2];
(a[0], a[1]) = (1, 23);

//--- プロパティに入れる
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
var p = new Person();
(p.Name, p.Age) = ("xin9le", 31);

逆コンパイル

あまりに見慣れない書き方過ぎてもはやワケわからん状態かと思うので (?)、逆コンパイルしてどう実現されているかを確認してみましょう。

//--- これは
var (x, y) = (123, "abc");

//--- こう展開される
ValueTuple<int, string> t = new ValueTuple<int, string>(123, "abc");
int x = t.Item1;
string y = t.Item2;

変数を並べた順に .Item1 .Item2 ...のプロパティが割り当てられていることが分かります。

暗黙的型変換

(当然ですが) 型を指定することで、暗黙的型変換の適用により互換のある型として値を受けとることができます。もちろん、互換のない型を指定するとコンパイルエラーになります。

var t = (123, "abc");
(long x, object y) = t;  //--- OK!!
(char x, string y) = t;  //--- int は char で受けられないので NG!!

任意の型の分解

ここまで ValueTuple 型の分解を見てきました。Deconstructions はタプル構文の対になる機能なので ValueTuple 型にしか適用できないのか、と言うと当然そんなことはなく、ちゃんと任意の型に対して適用できます。ただ、当然ながら何も追加実装をしなくても値の分解ができるわけではありません。既存の型をどのようなルールで分解するかがコンパイラには分からないからです。そのルールは以下のように Deconstruct メソッドを実装することで実現/決定します。

static void Main()
{
    var p = new Person();
    var (name, age) = p;  //--- ここで Deconstruct メソッドが呼ばれる
    Console.WriteLine($"({name}, {age})");  //--- (xin9le, 31)
}

class Person
{
    public string Name { get; } = "xin9le";
    public int Age { get; } = 31;

    //--- 型分解のための特殊なメソッド
    public void Deconstruct(out string name, out int age)
    {
        name = this.Name;
        age = this.Age;
    }
}

拡張メソッドとしての実装

上記の方法では Deconstruct メソッドをインスタンスメソッドとして実装しましたが、拡張メソッドとして実装しても OK です。以下のような感じです。

class Person
{
    public string Name { get; } = "xin9le";
    public int Age { get; } = 31;
}

static class PersonExtensions
{
    public static void Deconstruct(this Person self, out string name, out int age)
    {
        if (self == null)
            throw new ArgumentNullException(nameof(self));
        name = self.Name;
        age = self.Age;
    }
}

複数の分解方法の提供

Deconstruct メソッドはひとつでなければならないという制限はありません。複数の分解方法を提供したい場合は、複数の Deconstruct メソッドを実装することもできます。

static void Main()
{
    var v = new Vector3 { X = 1, Y = 2, Z = 3 };
    var (x2, y2) = v;      //--- 引数 2 つの方が呼び出される
    var (x3, y3, z3) = v;  //--- 引数 3 つの方が呼び出される
}

class Vector3
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }

    public void Deconstruct(out int x, out int y)
    {
        x = this.X;
        y = this.Y;
    }

    public void Deconstruct(out int x, out int y, out int z)
    {
        x = this.X;
        y = this.Y;
        z = this.Z;
    }
}

逆コンパイル

もう想像できているかもしれませんが、任意の型の分解がどのように実現されているかを確認してみましょう。以下のように展開されます。原理が分かれば簡単ですね!

var person = new Person();

//--- これは
var (name, age) = person;

//--- こう展開される (Deconstruct メソッドの呼び出しになる)
int x;
string y;
person.Deconstruct(out x, out y);
int name = x;
string age = y;

ちなみにすでにお気付きかもしれませんが、先に紹介した ValueTuple 型とは展開のされ方が全然違います。ValueTuple には Deconstruct メソッドの実装もないですし、かなり特別扱いされていることが分かります。

任意の型分解のまとめ

  • メソッド名は Deconstruct
  • 引数に out キーワードを付ける
  • 第 1 引数から順番に変数にマッピングされる
  • インスタンスメソッド or 拡張メソッドとして実装する
  • インスタンスメソッドと拡張メソッドがある場合はインスタンスメソッドが優先される
  • 分解方法 (Deconstruct メソッド) は複数あっても構わない

System.Tuple の分解

System.ValueTuple は特別扱いされて展開されることが分かりました。では参照型になっただけの System.Tuple はどうかと言うと、コンパイラによる特別扱いはありません。なので、同じように型を分解をするためには拡張メソッドの実装が必要になります。

//--- こんな感じ
public static class TupleExtensions
{
    public static void Deconstruct<T1, T2>(this Tuple<T1, T2> value, out T1 item1, out T2 item2)
    {
        item1 = value.Item1;
        item2 = value.Item2;
    }
}

しかし、さすがに毎度×2 個々人が実装するのは無慈悲過ぎるので、CoreFx では System.Tuple 型に対する Deconstruct 拡張メソッドを提供してくれています。よかった。

ちなみに、ValueTuple と似たような型に KeyValuePair がありますが、これも Deconstruct 拡張メソッドを用意しておくと幸せになれると思います。現状では提供されていませんが、Tuple と同様 .NET Framework / .NET Core に標準搭載されるかも (?) しれません。

こんなこともできる / これはできない

ちょっとオマケではありますが、挙動を調べてみたものを並べてみます。執筆時点での話なので、できないものも将来的にできるようになるかもしれません。

foreach で使える

変数を受けるのは、当然 foreach でもできます。場合によってはシンプルになって良いかもしれませんね。

var collection = new []
{
    (1, 'a'),
    (2, 'b'),
    (3, 'c'),
};
foreach (var (x, y) in collection)  //--- こんなのも OK
    Console.WriteLine($"({x}, {y})");

クエリ式では使えない

foreach で使えたのでクエリ式でも行けるかも!と思ったけれど、使えませんでした。

//--- できてほしいけれど、現状はまだダメ
var query = from (x, y) in collection
            where x >= 2
            select y;

out var では使えない

先日紹介した変数宣言式 (= Out Variable Declarations) で使えるかも試してみましたが、ここでも使えませんでした。できたら結構便利そうなんだけどなぁ...。

bool OutVar(out ValueTuple<int, string> result)
{
    result = (123, "abc");
    return true;
}

if (OutVar(out var (x, y)))  //--- コンパイルエラー
    Console.WriteLine($"({x}, {y})");

入れ子の分解ができる

入れ子になっていてもちゃんと分解できます。なんとも親切!

//--- こんな入れ子になったものも
(int, (string, char)) GetValueTuple()
    => (1, ("abc", 'あ'));

//--- 分解/展開できちゃう!
var (x, (y, z)) = GetValueTuple();
Console.WriteLine($"({x}, {y}, {z})");  //--- (1, abc, あ)

超簡単スワップ! キタ━(゚∀゚)━!

タプル構文と型分解のおかげで、値のスワップが神がかり的に簡単になります!.Swap(x, y) メソッドよ、安らかに眠れ... (-人-)

var x = 1;
var y = 2;
(x, y) = (y, x);  //--- なんとこれだけ!直観的!
Console.WriteLine($"({x}, {y})");  //--- (2, 1)

タスクバーのアイコン表示が壊れたときの対処方法

Windows 10 を使っていると、タスクバーに表示される Microsoft Edge などの UWP 関連アプリのアイコンの表示がおかしくなるときがあります。以下の画像で言うところの、Edge とか Store のアイコンみたいな感じです。今回はこれにハマったのでメモ。

f:id:xin9le:20160712204131p:plain

原因と解決方法

こうなる原因としては、アイコンのキャッシュが良くない形で生成されているためです。なんでそんな不正なキャッシュができるのかは分かりませんが、SageThumbs などを使っていると発生しやすいようです。とは言え、キャッシュを消してやれば直ります。悪さをしてると思われるアイコンキャッシュは以下。

  • %LOCALAPPDATA%\IconCache.db
  • %LOCALAPPDATA%\Microsoft\Windows\Explorer\iconcache_*.db
  • %LOCALAPPDATA%\Microsoft\Windows\Explorer\thumbcache_*.db

エクスプローラーがこれらのキャッシュファイルを握ってしまっていて消せなかったりするので、削除する際はエクスプローラーを強制終了してやる必要があります。

削除コマンド

ということで削除するためのコマンドを用意しました。以下のコードを RemoveIconCache.cmd などとして保存し、実行してください。

@echo off

rem //--- エクスプローラーを終了
taskkill /f /im explorer.exe

rem //--- 削除確認
echo アイコンキャッシュ消しちゃっうけどいいかなー?(いいともー
pause

rem //--- 削除
del /q /f %LOCALAPPDATA%\IconCache.db
del /q /f %LOCALAPPDATA%\Microsoft\Windows\Explorer\iconcache_*.db
del /q /f %LOCALAPPDATA%\Microsoft\Windows\Explorer\thumbcache_*.db

rem //--- エクスプローラー再開
explorer.exe
exit

簡単ですね!これでもうアイコンキャッシュの破損に悩まされないハズ!

変数宣言式

約一か月前に C# 7 の新機能についてライブコーディングをしたと思ったら、もうその情報だけでは足りていなくて焦っている今日この頃。

あれから Roslyn のリポジトリ には大きな変化がありました。

  1. future ブランチが master ブランチにマージされた!
  2. いくつかの新機能がさらに追加されている!

もはや毎日進捗を見てないとダメじゃないかと思うくらい、開発の速さに驚いています。最近新たに追加された新機能について実際に動かしてみたものベースで紹介して行きます。

out var

C# 6 のときに一度導入が検討されたけど見送りになった機能、変数宣言式が帰ってきました!前バージョンでの紹介の名残で変数宣言式と書いていますが、英語では「Out Variable Declarations」と呼ばれています。「返り変数宣言」とか訳したりするのかな...(知らんけど

どんな機能なのかは C# 6 のときに書いたものとほぼ同じなので、以下の記事をご参照くださいw (手抜き

簡単に概略を説明すると以下のような感じです。

//--- これまでのこんなコードを...
static void Main()
{
    int value;  //--- イチイチ前に変数を宣言
    if (int.TryParse("123", out value))
        Console.WriteLine(value);
}

//--- こう書けるようになる!
static void Main()
{
    if (int.TryParse("123", out var value))  //--- 式の途中で変数を宣言して
        Console.WriteLine(value);            //--- それを利用する
}

必須じゃないけど地味に嬉しい、そんな機能かなと思います。

C# 6 で検討されていた仕様との差分

C# 6 のときは out 宣言した変数の寿命は、宣言したステートメントのスコープに限られました。C# 7 ではそのスコープに限定せず、通常の out 変数の利用と同じスコープで変数が有効になりました

//--- C# 6 の頃に検討されていた仕様
static void Main()
{
    if (int.TryParse("123", out var value))
    {
        //--- このスコープでしか value 変数を使えない
        Console.WriteLine(value);
    }
}

//--- C# 7 ではこうなる
static void Main()
{
    if (int.TryParse("123", out var value))
    {
        //--- このスコープでも当然使えるし
        Console.WriteLine(value);
    }

    //--- ここでも使える
    Console.WriteLine(value);    
}

なので、同一変数名で何度も TryParse な if 文を並べるようなことはできなくなりますが、これまでの out 変数と同じ挙動を優先したのだと推測します。