WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

全角数字を半角に変換

by WebSurfer 2016年1月15日 17:58

全角 / 半角の変換には Microsoft.VisualBasic 名前空間の Strings.StrConv メソッドがよく使われているようですが、それは使わないで、全角数字を半角に変換する方法を書きます。

基本的には Regex.Replace メソッド (String, MatchEvaluator) を使って文字列の中の全角数字の部分を半角数字に置き換えるのですが、問題は引数の MatchEvaluator に設定するカスタムメソッドをどのように作るかです。

@IT の記事「全角英数字のみを半角に変換するには?」にその例がありますが、そこでは Strings.StrConv メソッドが使われています。

しかしながら、Strings.StrConv メソッドはローケルの違いとか XP 互換モードで期待した結果にならないという問題があるそうです。

数字だけなら辞書を作るのは簡単ですので、Strings.StrConv メソッドを使う代わりに、IEnumerable.Select 拡張メソッドと辞書を使ってカスタムメソッドを実装してみました。

以下にコード例を書きます。その中の Replacer というのが MatchEvaluator に設定するカスタムメソッドです。

Replacer のコードの中で、m.Value はマッチした全角数字の文字列(String 型。IEnumerable<char> を継承している)になります。Select 拡張メソッドは辞書を使って全角数字の文字列の各文字 (Char 型)を半角に置き換え、それを IEnumerable<char> 型のオブジェクトとして返します。

半角数字に置き換えられた IEnumerable<char> 型のオブジェクトを ToArray メソッドで Char[] に変換し、String(Char[]) コンストラクタで半角数字の文字列(String 型)を生成して Replacer メソッドの戻り値として返しています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace ConsoleApplication
{
  class Program
  {
    // 辞書を作る
    public static Dictionary<char, char> dictionary =
      new Dictionary<char, char>() {
        {'0','0'},{'1','1'},{'2','2'},{'3','3'},
        {'4','4'},{'5','5'},{'6','6'},{'7','7'},
        {'8','8'},{'9','9'}
      };
        
    public static string Convert(string source)
    {
      Regex regex = new Regex("[0-9]+");
      return regex.Replace(source, Replacer);
    }

    public static String Replacer(Match m)
    {
      return new String(
        m.Value.Select(n => dictionary[n]).ToArray());
    }

    static void Main(string[] args)
    {
      String source = "0123456789Ab1C02dEfあい36";
            
      Console.WriteLine(source);
      Console.WriteLine(Convert(source));

      // Regex を使わなくても以下のようにして可能
      String replaced = new String(
        source.Select(
          n => (dictionary.ContainsKey(n) ? dictionary[n] : n)
          ).ToArray()
        );
      Console.WriteLine(replaced);
            
      /* 結果は:
      0123456789Ab1C02dEfあい36
      0123456789Ab1C02dEfあい36
      0123456789Ab1C02dEfあい36
      */
    }
  }
}

Tags: ,

.NET Framework

VB.NET の List(Of T) と Count プロパティ

by WebSurfer 2015年12月12日 15:09

VB.NET の場合、定義されているプロパティが同じクラスで定義されている(もしくは継承したクラスで定義されている)同名のメソッドを隠してしまうという話を、List(Of T) と Count プロパティを例にとって説明します。

例として以下のコードを考えます。

Dim e As IEnumerable(Of Int32) = Enumerable.Range(1, 10)

' これは OK。拡張メソッド Count を呼び出す
Console.WriteLine(e.Count(Function(n) n >= 5))

Dim x As List(Of Int32) = e.ToList

' ビルドエラー「Public ReadOnly Property Count As Integer' には引
' 数がないため、戻り値の型をインデックス化できません。」になる
Console.WriteLine(x.Count(Function(n) n >= 5))

'明示的に拡張メソッドを呼び出せば OK
Console.WriteLine(Enumerable.Count(x, Function(n) n >= 5))

e は IEnumerable(Of T) 型で Count という名前では拡張メソッドの Count のみを実装しています。(同名のプロティは実装されていません)

なので、e.Count(Function(n) n >= 5) で拡張メソッドの Count を呼び出せています。

一方、x は IEnumerable(Of T) から ToList メソッド を使って作成した List(Of T) 型になります。List(Of T) 型には Count プロパティCount 拡張メソッド(Enumerable.Count のオーバーロード)の両方が実装されています。

VB.NET の場合、List(Of T) 型に実装されている Count プロパティによって拡張メソッドの Count が隠されてしまい、コンパイラは Count プロパティが呼ばれていると判断して上のソースにコメントしたようなビルドエラーになります。

これは VB.NET のコンパイラの問題で、同名のプロパティとメソッドを区別できないところからきているようです。

そのことを書いた Micosoft の公式文書は見つかりませんでしたが、MSDN フォーラムの記事で、Matt Warren - MSFT さんが、

"VB compiler is matching the lists Count property instead of the Enumerable.Count() extension method and the Count property is blocking the visibility of the extension method's other signature."

Timothy Ng MSFT さんが、

"in VB, properties shadow methods (and thus, extension methods) by name."

と書いた説明で自分的には納得しています。

そのことは List(Of T) と Count プロパティ / メソッドに限った話ではなく、以下のような自作のクラスで定義した同名のプロパティとメソッドでも再現できます。

Module Module1
    Sub Main()
        Dim sample As SampleClass = New SampleClass()

        ' SampleClass の Name プロパティが呼ばれる
        Console.WriteLine(sample.Name)

        ' 同じく SampleClass の Name プロパティが呼ばれる。
        ' VB.NET の ( ) は、C# の [ ] と同様に、配列の要素にアク
        ' セスするので、sample.Name(1) は "Property called." の 
        ' 2 文字目の 'r' となる。
        Console.WriteLine(sample.Name(1))

        ' 同じく SampleClass の Name プロパティが呼ばれる。
        ' ( ) は文字列(配列)"Property called." の要素にアクセ
        ' スする。引数 "abc" は Int32 型に変換できないのでビルド
        ' エラーになる。
        Console.WriteLine(sample.Name("abc"))
    End Sub
End Module

Public Class SampleClass
    Inherits SampleBaseClass
    ' Shadows キーワードを付けないと以下の警告が出るが、付けな
    ' くても結局は shadow される。
    ' 「property 'Name' は、function 'Name' とベース class 
    ' 'SampleBaseClass' で競合しています。'Shadows' として宣言
    ' されなければなりません。}
    Public ReadOnly Property Name() As String
        Get
            Return "Property called."
        End Get
    End Property
End Class

Public Class SampleBaseClass
    Public Function Name(ByVal i As Int32) As String
        Return "Instanse Method called."
    End Function
End Class

Public Module MyExtension
    <System.Runtime.CompilerServices.Extension()>
    Public Function Name(ByVal x As SampleBaseClass, _ 
                         ByVal s As String) As String
        Return "Extension Method called."
    End Function
End Module

なお、C# の場合はこのような問題なく、以下のコードでいずれも期待した通りの結果 6 を取得できます。

IEnumerable<int> e = Enumerable.Range(1, 10);

Console.WriteLine(e.Count(n => n >= 5));

List<int> x = e.ToList();

Console.WriteLine(x.Count(n => n >= 5));
Console.WriteLine(Enumerable.Count(x, n => n >= 5));

Tags: , ,

.NET Framework

匿名型と Distinct メソッド

by WebSurfer 2015年12月8日 19:44

IEnumerable<T> で T が匿名型の場合は Distinct() メソッドで期待通り重複のない結果が得られますが、T にカスタムデータ型を使った場合は、

  1. そのカスタムデータ型に IEquatable<T> インターフェイスを継承させて Equals メソッドを実装し、GetHashCode メソッドを override する(具体例は MSDN ライブラリ Enumerable.Distinct<TSource> メソッド (IEnumerable<TSource>) のサンプルコード参照)、または、
  2. IEqualityComparer<T> を継承したクラスを作成し、それを引数にとって値を比較できるバージョンの Distinct メソッドを使う(具体例は MSDN ライブラリ Enumerable.Distinct<TSource> メソッド (IEnumerable<TSource>, IEqualityComparer<TSource>) のサンプルコード参照)。

のいずれかの方法を取る必要があるそうです。知ってました? 実は自分は知らなかったです。匿名型を使っている限りは問題なかったので。(汗)

ちなみに、上に紹介した MSDN ライブラリのサンプルコードで、Product 型に替えて匿名型を使えば以下のように Distinct() メソッドを使って重複しない結果を取得できます。

var products = new[] { 
    new { Name = "apple", Code = 9 }, 
    new { Name = "orange", Code = 4 }, 
    new { Name = "apple", Code = 9 }, 
    new { Name = "lemon", Code = 12 } };

var noduplicates = products.Distinct();

foreach (var product in noduplicates)
    Console.WriteLine(product.Name + " " + product.Code);

/* 結果は:
apple 9
orange 4
lemon 12
*/

ただ、それで何故 Distinct() が使えるのかがズバリ書いてある Microsoft の公式文書が見つからないという不安はありますが。

一応、匿名型における Equals, GetHashCode, ToString メソッドおよび IEquatable<T> インターフェイスの実装について以下の文書があるのは見つけました。

上のコード new { Name = "apple", Code = 9 } で作られる匿名型にも上記が当てはまるのであろうとは思っていますが・・・

(注)VB.NET には Key というキーワードがあってそれを付与して定義したプロパティのみが比較の対象となるようです。一方、C# は上の記事にもあるように全てのプロパティが比較の対象になります。

Tags: ,

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar