RunningCSharp

MS系開発者による、雑多な記事。記事は所属企業とは関係のない、個人の見解です。

C#で書いたラムダ式を用いたコードをVBで書き直してみる

.netのVBを使い慣れない私が、今度はラムダ式を使ったテストコード(コンソールアプリケーション)をC#で書いた後、そのコードVBに書き直してみただけの記事です。

C#

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            //文字列を返すだけのFunc
            Func<string> func = () => "returnvalue";
            //数値を3倍にして文字列で返すFunc
            Func<int, string> func2 = (i) =>
            {
                i = i * 3;
                return i.ToString();
            };
            //引数をコンソールに表示するだけのAction
            Action<string,string> action = (str1,str2) => 
            {
                Console.WriteLine(str1);
                Console.WriteLine(str2);
            };
            //Actionを実行
            action(func(), func2(2));

            //下記コードはコンソール画面を表示しておくために追加
            Console.ReadLine();
        }
    }
}

上記処理をVB(Visual Studio 2015)で書き直してみます。

Module Module1
    Sub Main()
        '文字列を返すだけのFunc
        Dim func As Func(Of String) = Function() "returnstring"
        '数値を3倍にして文字列で返すFunc
        Dim func2 As Func(Of String, Integer) =
            Function(i)
                i = i * 3
                Return i.ToString()
            End Function
        '引数をコンソールに表示するだけのAction
        Dim action As Action(Of String, String) =
        Sub(str1, str2)
            Console.WriteLine(str1)
            Console.WriteLine(str2)
        End Sub
        'Actionを実行
        action(func(), func2(2))

        '下記コードはコンソール画面を表示しておくために追加
        Console.ReadLine()
    End Sub
End Module

処理結果はどちらも

returnstring

6

となります。

LinqGUIのイベントにと使い道は非常に多いので、VBでのラムダ式の書き方は押さえておくと便利そうと感じました。 とりあえず書き溜めて、そのうちQiitaとかで纏めようかと思います。

C#で書いたイテレーターを用いたコードをVBで書き直してみる

.netのVBを使い慣れない私が、今度はイテレーターを使ったテストコード(コンソールアプリケーション)をC#で書いた後、そのコードVBに書き直してみただけの記事です。

C#

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (string message in TestList())
            {
                Console.WriteLine(message);
            }
            //下記コードはコンソール画面を表示しておくために追加
            Console.ReadLine();
        }

        static IEnumerable<string> TestList()
        {
            yield return "I";
            yield return "have";
            yield return "a";
            yield return "pen";
        }
    }
}

上記処理をVB(Visual Studio 2015)で書き直してみます。

Module Module1

    Sub Main()
        For Each message In TestList()
            Console.WriteLine(message)
        Next
        '下記コードはコンソール画面を表示しておくために追加
        Console.ReadLine()
    End Sub

    Iterator Function TestList() As IEnumerable(Of String)
        Yield "I"
        Yield "have"
        Yield "a"
        Yield "pen"
    End Function

End Module

イテレーター自体はあまり多用しないので微妙ですが、ジェネリックの書き方の違いが見やすいかなと感じました。 とりあえず書き溜めて、そのうちQiitaとかで纏めようかと思います。

C#で書いたカスタム属性をVB(.net)でも書いてみる

VBを使い慣れない私が、カスタム属性クラスを適用するテストコードをC#で書いた後、そのコードVBに書き直してみただけの記事です。

C#

    //文字列を保持するだけの属性
    public class TestAttribute : Attribute
    {
        private string val;
        //属性のコンストラクタ
        public TestAttribute(string testValue)
        {
            val = testValue;
        }
        //コンストラクタで入れた文字列を返すプロパティ
        public string Value
        {
            get { return val; }
        }
    }
    //Test属性を適用したクラス
    [Test("TestValue")]
    public class TestClass
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            //TestClassに指定されたTestAttributeを取得
            var att = (TestAttribute)Attribute.GetCustomAttribute(typeof(TestClass), typeof(TestAttribute));
            //TestAttributeのValueを取得
            Console.WriteLine(att.Value);
        }
    }

上記処理をVB(Visual Studio 2015)で書き直してみます。

'文字列を保持するだけの属性
Public Class TestAttribute
    Inherits System.Attribute
    Private val As String
    '属性のコンストラクタ
    Public Sub New(ByVal testValue As String)
        val = testValue
    End Sub
    'コンストラクタで入れた文字列を返すプロパティ
    Public ReadOnly Property Value() As String
        Get
            Return val
        End Get
    End Property
End Class

'Test属性を適用したクラス
<Test("TestValue")>
Public Class TestClass
End Class

Module Module1

    Sub Main()
        'TestClassに指定されたTestAttributeを取得
        Dim att As TestAttribute = Attribute.GetCustomAttribute(GetType(TestClass), GetType(TestAttribute))
        'TestAttributeのValueを取得
        Console.WriteLine(att.Value)
    End Sub

End Module

書いてみた結果、継承、コンストラクタ、プロパティ定義やら、C#VBでの書き方の違いを見比べられて少し良いなと思いました。

C#ユーザーがVBの遅延バインディングに驚いた話など

色々あって、Visual Studio 2008でVBをやっています。

触ってみた結果、多数のスタティックメソッドや「改行にアンダーバー必須」といった文法関連など色々なC#との使い勝手の違いを感じましたが、一番違いに驚いたのは下記コードの動作です。

    Dim testobj As Object
    'オブジェクト型の変数に日付型を代入
    testobj = DateTime.Now
    'Objectクラスが持っていない「Month」プロパティを、遅延バインディングで使える
    Console.WriteLine(testobj.Month & "月")

思い起こせば、VB6の頃は当たり前のように遅延バインディングを使ったコードを書いていましたが、今見るとtestobjにMonthプロパティがないクラスのインスタンスを入れると実行時エラーが出せてしまう事から、少々おっかない印象を受けてしまいます。

なお上記のコードをC#で動くように書くと、下記のようになります。

    object testobj;
    testobj = DateTime.Now;
    //TypeクラスのGetPropertyメソッドでプロパティ情報を取得し、オブジェクトのプロパティ値取得を行う
    Console.WriteLine(testobj.GetType().GetProperty("Month").GetValue(testobj).ToString() + "月");

リフレクションを用いてプロパティ情報を取得した後、プロパティの実際の値を取得する流れになります。

少々冗長な気もしますが、リフレクションを使うと下記のように、変数testobjのプロパティの有無をチェックする事が容易です。

    object testobj;
    testobj = DateTime.Now;
    //Monthプロパティを取得
    System.Reflection.PropertyInfo prop = testobj.GetType().GetProperty("Month");
    if (prop != null)
    {//testobjに入ったオブジェクトに、Monthプロパティが存在する場合にのみ処理が実行される
        Console.WriteLine(prop.GetValue(testobj).ToString() + "月");
    }

プロパティの有無をチェックすることで、実行時エラーを防げます。

もちろんVBでも、リフレクションを用いてプロパティの有無をチェックできます。

    Dim testobj As Object
    testobj = DateTime.Now
    'リフレクションでMonthプロパティの情報を取得
    Dim prop As System.Reflection.PropertyInfo = testobj.GetType().GetProperty("Month")
    If (prop <> Nothing) Then
        'testobjにMonthプロパティが存在する場合のみ、処理が実行される
        Console.WriteLine(testobj.Month & "月")
    End If

コードは長くなりますが、遅延バインディングの対象となるオブジェクト型の変数に何が入ってくるかわからない時にはぜひこのチェックは行ったほうが良さそうです。

遅延バインディングを止めて、きちんと型チェックをしてから実行したい場合は下記のような感じで。

Option Strict OnDim testobj As Object
    testobj = DateTime.Now
    'リフレクションでMonthプロパティの情報を取得
    Dim prop As System.Reflection.PropertyInfo = testobj.GetType().GetProperty("Month")
    If (prop <> Nothing) Then
        'testobjにMonthプロパティが存在する場合のみ、処理が実行される
        Console.WriteLine(prop.GetValue(testobj).ToString() & "月")
    End If

なお、C#4.0からはdynamic型が使えるため、C#でも遅延バインディング的な書き方が可能です。

    dynamic testobj;
    testobj = DateTime.Now;
    Console.WriteLine(testobj.Month + "月");

VBの遅延バインディングと同じ理由で、testobjの内容に変更が多い場合はリフレクションのほうがお勧めです。

WPF:プロパティ値の継承(包含継承)が可能な添付プロパティ

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <GroupBox Header="test" Foreground="Red">
        <StackPanel Orientation="Vertical">
            <TextBlock Text="test" />
            <TextBox Text="test"/>
            <Button Content="test" />
        </StackPanel>
    </GroupBox>
</Window>

上記のようなxamlを実行した場合、GroupBoxのForegroundプロパティの値を子のアイテムが引き継ぐことがあります。 この動きを、プロパティの値の継承と呼びます。(クラスの派生による継承とは異なることから、「包含継承」と呼ぶことがあるようです。)

上記の例では、TextBlockはGroupBoxのForegroundを引き継ぎますが、TextBoxやButtonのForegroundは引き継がれないようです。

f:id:ys-soniclab:20160910215003p:plain

そこで、プロパティ値の継承が可能な添付プロパティを作成し、そのプロパティが設定されたコントロールのForegruondを強制的に書き換えるようにしてみます。

public class Att
{
    public static Brush GetForeground(DependencyObject obj)
    {
        return (Brush)obj.GetValue(ForegroundProperty);
    }

    public static void SetForeground(DependencyObject obj, Brush value)
    {
        obj.SetValue(ForegroundProperty, value);
    }

    public static readonly DependencyProperty ForegroundProperty =
        DependencyProperty.RegisterAttached("Foreground", typeof(Brush), typeof(Att), new FrameworkPropertyMetadata(null, OnForegroundChanged) { Inherits = true });

    private static void OnForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ctl = d as Control;
        if (ctl == null) return;
        ctl.Foreground = e.NewValue as Brush;
    }
}
<Window …>
    <GroupBox Header="test" local:Att.Foreground="Red">
        <StackPanel Orientation="Vertical">
            <TextBlock Text="test" />
            <TextBox Text="test"/>
            <Button Content="test" />
        </StackPanel>
    </GroupBox>
</Window>

上記の添付プロパティを付けると、Foregroundが強制的に変更されます。

f:id:ys-soniclab:20160910215020p:plain

Xamarin:Key指定なしのStyle(デフォルトのStyle)を継承する方法

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="App.MainPage"
             xmlns:local="clr-namespace:App">
  <ContentPage.Resources>
    <ResourceDictionary>
      <Style TargetType="Button">
        <Setter Property="BackgroundColor" Value="Red"/>
      </Style>
    </ResourceDictionary>
  </ContentPage.Resources>
  
  <StackLayout Orientation="Vertical">
    <Button Text="Test1"/>
    <Button>
      <Button.Style>
        <Style TargetType="Button">
          <Setter Property="Text" Value="Test2"/>
        </Style>
      </Button.Style>
    </Button>
  </StackLayout>
</ContentPage>

上記のようなxamlを記述した場合、ContentPageの子要素でButtonを定義した場合、基本的には自動的にContent.Resourcesで定義したKeyを指定していないStyleが適用されます。そのため、上のボタンは何も指定していなくともBackgroundが赤くなります。

しかし、下のボタンはButtonのStyleプロパティに新規でStyleを指定しているため、Content.Resourcesで定義したStyleが適用されません。

f:id:ys-soniclab:20160906222106p:plain

下のボタンにもContent.ResourcesのStyle(つまり、その場所での暗黙的なStyle)を継承させるには、

WPFではBasedOn="{StaticResource {x:Type Button}}"のような書き方をしましたが、Xamarinでは下記のようにBasedOnを記述します。

    <Button>
      <Button.Style>
        <Style TargetType="Button" BasedOn="{StaticResource Xamarin.Forms.Button}">
          <Setter Property="Text" Value="Test2"/>
        </Style>
      </Button.Style>
    </Button>

Buttonを上記のように書き換えると、標準のStyleを継承することが出来ます。

f:id:ys-soniclab:20160906222120p:plain

Xamarin:PCLに埋め込みリソースとしてテキストなどのファイルを配置し、そのファイルを読み込む

PCLプロジェクトに「埋め込みリソース」としてファイルを配置し、そのファイルを読み込む例を記載します。 今回の例はxmlファイルですが、テキストでも同様の方法で読み込めます。

下記の配置のファイルを読み込む例です。

f:id:ys-soniclab:20160905213320p:plain

//typeofの引数には、このメソッドが実装されているクラスを指定してください
var assembly = typeof(MainPageViewModel).GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream("FlashCard.Data.initialData.xml"))
using (StreamReader reader = new StreamReader(stream))
{
    //下記例は両方実行しようとするとエラーになるため、文字列かxmlどちらかのみ実行してください。

    //文字列として読み込む例
    string str = await reader.ReadToEndAsync();
    //xmlをデシリアライズする例
    System.Xml.Serialization.XmlSerializer serializer =
        new System.Xml.Serialization.XmlSerializer(typeof(FlashCardList));
    var list = serializer.Deserialize(reader) as FlashCardList;
}