2014年06月18日

属性 Enum(列挙体)への適用と拡張メソッドへの応用

属性とはクラスやそのメンバなどに付与するメタ情報

下記のように[]で囲んで指定する。
以下のSystem.ObsoleteAttributeは互換性のために残してあるが使用が推奨されないことをVisualStudioを介して明示する。
[Obsolete("今後のリリースで削除予定")]
public void Sample(){};
独自実装 属性は独自に実装することも可能。
 System.Attributeクラスを継承する。
 HogeHogeAttributeのように接尾辞にAttributeを付与する。
 System.AttributeUsageAttributeで適応対象を指定する

以下は列挙体定義に対し、列挙体名と列挙子名に別名を付与するサンプル
    [AttributeUsage(AttributeTargets.Enum|AttributeTargets.Field)] // 適用対象指定
    public class AliasAttribute:Attribute                          // Attributeを継承
    {
        private string _text;                                      // バックストア
        public string Text { get { return _text; } }               // 文字列取得用プロパティ
        public AliasAttribute(string text) { _text = text; }       // 文字列を1つとるコンストラクタ 
    }
この属性を列挙体に付与したサンプル
    [Alias("四国地方")]
    enum ShikokuPrefecture
    {
        [Alias("愛媛県")]
        Ehime,
        [Alias("香川県")]
        Kagawa,
        [Alias("高知県")]
        Kouchi,
        [Alias("徳島県")]
        Tokushima
    }
属性や属性の値はリフレクションにより取得する。 以下は拡張メソッドとしてAlias属性が付与されている場合はその値を、無い場合はNullを返すサンプル
using System.Reflection;   // これも必要

    public static class EnumExtensions
    {
        // 列挙値に付与されている別名の取得
        public static string ToAliasString(this Enum target)
        {
            var attribute = target.GetType().GetMember(target.ToString())[0].GetCustomAttribute(typeof(AliasAttribute));

            return attribute == null ? null : ((AliasAttribute)attribute).Text;
        }
        // 列挙体に付与されている別名の取得
        public static string ToAliasEnumString(this Enum target)
        {
            Attribute attribute = target.GetType().GetCustomAttribute(typeof(AliasAttribute));

            return attribute == null ? null : ((AliasAttribute)attribute).Text;
        }
    }
以上を使用したサンプルの実行結果
ShikokuPrefecture sk = ShikokuPrefecture.Ehime;
string test1 = sk.ToString();           // 実行結果:Ehime
string test2 = sk.ToAliasString();      // 実行結果:愛媛県
string test3 = sk.ToAliasEnumString();  // 実行結果:四国地方
メモ
フラグ値や区分値は内部に保持する値と表示文字列とで異なる場合が多い。コンバータやマップで対応を保持するには定義が冗長で分散せざるを得ない。このような場合にメタ情報や拡張メソッドは都合よくはまるようです。
posted by RR at 03:42 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2014年10月19日

変数の型 object var dynamic

強い型付け

C#は基本的には強い型付けの言語。
宣言される変数の型とそこに代入される変数の型は一致している必要がある。
若しくは、継承関係がある必要がある。
    interface Car
    {
        void Drive();
    }

    class Bus : Car
    {
        public void Drive()
        {
            Console.WriteLine("Drive a bus");
        }
    }

    class Bike
    {
        public void Drive()
        {
            Console.WriteLine("Drive a bike");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 左辺の変数の型と右辺が一致
            Bus bus1 = new Bus();
            bus1.Drive();
            // 左辺の型と右辺の型とに継承関係あり
            Car bus2 = new Bus();
            bus2.Drive();
            // 左辺の型と右辺とは関係ないのでコンパイルが通らない
            //Car bike = new Bike();
        }
    }

object

全ての型はobject型を継承しているのでobject型ならどんな型も代入可能。
但し、object型は参照型なので値型の代入には暗黙的にBoxingという処理が介在するのでパフォーマンスがよくない。
型のメンバを使うには、結局のところキャストが必要となる。
            // どんな型でもOK
            object bus3 = new Bus();
            // ただしメンバを使うにはキャストが必要
            ((Car)bus3).Drive();
            // 数値型でも文字列型でもOK
            object num1 = 1;
            num1 = "1";

var

暗黙的に型指定されるローカル変数。
ローカルスコープだけで使用可能。
コンパイル時に型が決まる。
VisualStudioのエディタで変数をポイントすると型も表示される。
            // なんの型でもOK
            var bike = new Bike();
            // コンパイラによりこの時点で数値型(int)と推定されている。
            var num2 = 2;
            // なので以下のように数値型に文字列型は入らない。
            //num2 = "2";

dynamic

実行時に型が決まる動的型付け変数
C#4.0以降で使用可能。
リフレクションの代替として使える。
           dynamic bus4 = new Bus();
            bus4.Drive();

            dynamic bike = new Bike();
            bike.Drive();
            // 以下のようなのもコンパイルは通る(実行時に例外発生)
            bike.Fly();

使いどころ

objectやdynamicは必要なところに使うが、varは議論あり。
自分で書いて自分で読むだけなら全部varでも問題ないけど他人様が読む必要がある場合は可読性の低下を招く場合がある。
    // 右辺から左辺の型が一目瞭然
    var reader = new System.IO.StreamReader();
    
    // わからん
    var restlt = this.Execute(list);

posted by RR at 11:15 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2014年11月11日

ちょっと変わった変数の宣言 Lazy WeakReference Threadlocal

Lazy

遅延初期化と呼ばれるもの。
実際に使われる時点(Valueプロパティにアクセスした時点)で初期化が行われる。
using System;
    public class Data
    {
        public List<int> List { get; set; }
        public Data(int length)
        {
            List = new List<int>();
            for(int n = 0 ; n < length ; n++) List.Add(n);
        }
    }

    class Tester
    {
        static void Main(string[] args)
        {
            // 宣言:インスタンス化する場合は引数のActionが使われる
            Lazy<Data> _data = new Lazy<Data>(() => new Data(100));
            // 値はFalse まだインスタンス化されていない
            bool assert = _data.IsValueCreated;
            // ここでValueにアクセスするのでインスタンス化が行われる。値は100
            int count = _data.Value.List.Count;
            // ここではTrueになっている
            assert = _data.IsValueCreated;
        }
    }



WeakReference

※別途記載


ThreadLocal

普通の変数のようだが、スレッド毎に異なる値を持つ。
スレッドプールのようにスレッドを使いまわす場合は要注意。
using System.Threading.Tasks;
using System.Threading;

            ThreadLocal<int> tl = new ThreadLocal<int>();

            tl.Value = 10;

            Task task = Task.Factory.StartNew(() =>
            {
                // メインスレッドで10を入れているが、ここは別スレッド内なので値は0となる
                int assert2 = tl.Value;
                // ここで30を代入しても
                tl.Value = 30;
            });
            // ワーカスレッドの終了待ち
            task.Wait();
            // ワーカスレッド内で30を代入してもメインスレッド側では10のまま取得できる
            int assert3 = tl.Value;



posted by RR at 23:39 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2015年02月15日

型変換 (キャスト AS演算子 変換演算子 Object Mapper)

基本

型に厳密な言語なので、異なる型の変数に代入はできない。
    int number = 10;
    string name = "Ken";
    // name = number;  // これは型が違うのでコンパイルが通らない

    // 暗黙的に型を持っているので var も同じ
    var number = 10;
    var name = "Ken";
    // name = number;  // これもコンパイルが通らない

暗黙の変換

変換後も情報が欠落しないような場合には、異なる型に代入できるものもある。
    // 親クラス
    class BaseClass
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }
    // 派生クラス
    class DelivatedClass : BaseClass
    {
        public int Telephone { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
              // 32bitのintの値を64bitのlongに代入できる
              int a = 10;
              long b = a; 

              // 派生クラスは継承関係のある親クラスやインタフェースならOK
              DelivatedClass dc = new DelivatedClass();
              BaseClass bc = dc;
        } 

キャストとAS演算子

型を変換すると問題がある場合も想定されるが、プログラムを書く側がそれで問題ないと明示的に指定することも可能。
    // 親クラス
    class BaseClass
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }
    // 派生クラス
    class DelivatedClass : BaseClass
    {
        public int Telephone { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
              // 64bitのlongを32bitのintに代入。
              long a = 10;
              //int b = a; これはできない
              int b = (int)a;  // 明示的にキャストすれば文法上はOK aの値が32bitを超えてると不正な値となる。

              // 変数は親クラス型だが、インスタンスは派生型なら情報の欠落はない。
              BaseClass bc = new DelivatedClass();
              DelivatedClass dc = (DelivatedClass)bc;

              // インスタンス自体が親クラスだとコンパイルは通るが実行時エラーとなる
              BaseClass bc = new BaseClass();
              DelivatedClass dc = (DelivatedClass)bc; //throws CastInvalidException

              // 参照型ではキャストと同じようにAS演算子も使える。
              // 変換できない場合は例外をスローするのではなくnullを返却する
              BaseClass bc = new BaseClass();
              DelivatedClass dc = bc as DelivatedClass;
        } 

ToStringメソッドとParseメソッド

全ての型はObjectクラスから派生しており、Object型はstring型を返却するToStringメソッドを持つ。
よって、すべての型はToStringメソッドを持つが、返却される文字列が何を表すかは各クラスの定義次第。
一方、基本的なクラスや構造体の多くは文字列等から自身の型に変換するためのメソッドを持っている。
    int number = 10;
    string temp1 = number.ToString(); // "10"という文字列が返却される

    DateTime dt = new DateTime(2015,02,15,19,24,32);
    string temp2 = dt.ToString();     // "2015/02/15 19:24:32" という文字列が返却される(※)

    // 文字列からの変換メソッドを持つものが多い
    int number2 = int.Parse("10");
    DateTime dt2 = DateTime.Parse("2015/02/15 19:25:08");

Converter

標準ライブラリにある System.Convertクラスには型変換用のメソッドが多数定義されている。
https://msdn.microsoft.com/ja-jp/library/system.convert(v=vs.110).aspx

     // こういうのがたくさんある
     bool b1 = Convert.ToBoolean("true");
     sbyte sb1 = Convert.ToSByte(-1);

型変換演算子


継承関係などがなくても、変換演算子を実装すればキャストが可能となる。
    class AA
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Telephone { get; set; }

        // 変換演算子の定義
        public static implicit operator BB(AA aa)
        {
            return new BB() { ID = aa.ID, Name = aa.Name };
        }
    }

    class BB 
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
             AA aa = new AA();
             BB bb = (BB)bb; // 変換演算子があればOK
    }


独自コンバーター

変換演算子は実装有無が外からわからないし、継承関係を誤解させる恐れがあり、可読性が下がる。
代わりに変換用のメソッドを実装する場合も多い。
    class AA : BaseClass
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Telephone { get; set; }

        // 変換用のメソッドを追加定義
        public BB ConvertToBB()
        {
            return new BB() { ID = this.ID, Name = this.Name };
        }
    }
    // 又は、拡張メソッドとして追加
    public static class AAEx
    {
        public static BB ConvertToBB(this AA aa)
        {
            return new BB() { ID = aa.ID, Name = aa.Name };
        }
    }


Object Mapping

上記のように、継承関係のないクラス間での変換が必要な場合は意外と多い。
例えば、Databaseのテーブル定義からリバースして作成されたクラスと、DTOクラス群との間など。
パフォーマンスは落ちるが実行時にマップ(コピー)する汎用の実装方法もある。
下記はpublicなプロパティ間で型と名前が一致すればコピーするサンプル
        using System.Reflection;

        static T Map<T>(object from) where T : new()
        {
            // 変換後の型のインスタンスを生成
            var ret = new T();
            // 変換後の型の全プロパティを対象に
            foreach (PropertyInfo pi in ret.GetType().GetProperties())
            {
                // Setterを取得 
                var setter = pi.GetSetMethod();
                // setterがpublicの場合
                if (setter != null)
                {
                    // 変換元の同名同タイプでgetterがpublicなプロパティを取得
                    var prop = from.GetType().GetProperties()
                                .Where(p => p.GetGetMethod() != null && p.Name == pi.Name && p.PropertyType == pi.PropertyType)
                                .SingleOrDefault();

                     // あればその値を設定
                    if(prop != null)
                        pi.SetValue(ret,prop.GetValue(from,null),null);
                }
            }
            return ret;
        }
posted by RR at 20:08 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2015年04月08日

クラスを定義するためのいくつかの方法

基本

例えば、年齢と名前とをメンバに持つクラスを定義する場合は以下のようなコードとなる。
            public class Person
            {
                public int Age { get; set; }
                public string Name { get; set; }
            }
このようなコードをコンパイルするとExeなりDLLなりのアセンブリファイルが生成される。
実行時にはそのファイル内に定義されたTypeを読みだしてインスタンス化して使用することになる。

他にもいくつか同様の機能を実装する方法がある。

匿名クラスとTuple

その場だけで使う場合などは変数名と値を指定して匿名クラスとして利用できる。型は推量される。
            var person = new { Age = 20, Name = "Taro" };
            int age = person.Age;
            string name = person.Name;

Tupleクラスを利用する場合は型と値を指定する。メンバにアクセスするにはItemNをつかう。
            var person = System.Tuple.Create<int, string>(23,"John");
            int age = person.Item1;
            string name = person.Item2;

CodeDomをOnMemoryで展開する

CodeDomとはコードの論理構造を表すもの。 ソースコードやアセンブリを動的に生成する場合などにも使われるが、実行時にオンメモリでコンパイルを行ってTypeを生成してインスタンス化することも可能。
コードは複雑そうだが、処理内容とクラス名やメソッド名が一致しているので可読性は高い。 自動プロパティが無いのでプライベートフィールドを別途定義し、プロパティはそのフィールドへのアクセッサとして実装している。

//using Microsoft.CSharp;
//using System;
//using System.CodeDom;
//using System.CodeDom.Compiler;
//using System.Reflection;

            var nameSpace = new CodeNamespace("SampleNameSpace");
            var classPerson = new CodeTypeDeclaration("Person");
            nameSpace.Types.Add(classPerson);

            var ageField = new CodeMemberField(typeof(int), "_age")
            {
                Attributes = MemberAttributes.Private | MemberAttributes.Final
            };
            classPerson.Members.Add(ageField);

            var ageProperty = new CodeMemberProperty
            {
                Name = "Age",
                Attributes = MemberAttributes.Public | MemberAttributes.Final,
                Type = new CodeTypeReference(typeof(int))
            };

            var ageFieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_age");

            ageProperty.GetStatements.Add(new CodeMethodReturnStatement(ageFieldReference));
            ageProperty.SetStatements.Add(new CodeAssignStatement(ageFieldReference, new CodePropertySetValueReferenceExpression()));
            classPerson.Members.Add(ageProperty);

            var nameField = new CodeMemberField(typeof(string), "_name")
            {
                Attributes = MemberAttributes.Private | MemberAttributes.Final
            };
            classPerson.Members.Add(nameField);

            var nameProperty = new CodeMemberProperty
            {
                Name = "Name",
                Attributes = MemberAttributes.Public | MemberAttributes.Final,
                Type = new CodeTypeReference(typeof(string))
            };
            var nameFieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_name");
            nameProperty.GetStatements.Add(new CodeMethodReturnStatement(nameFieldReference));
            nameProperty.SetStatements.Add(new CodeAssignStatement(nameFieldReference, new CodePropertySetValueReferenceExpression()));
            classPerson.Members.Add(nameProperty);

            using (var provider = new Microsoft.CSharp.CSharpCodeProvider())
            {
                var compileParameters = new System.CodeDom.Compiler.CompilerParameters
                {
                    GenerateInMemory = true,
                };
                var compileUnit = new CodeCompileUnit();
                compileUnit.Namespaces.Add(nameSpace);
                var result = provider.CompileAssemblyFromDom(compileParameters, compileUnit);
                // 生成したタイプは文字列指定で取得している
                var type = result.CompiledAssembly.GetType("SampleNameSpace.Person");
                // リフレクションでインスタンス化
                var person = Activator.CreateInstance(type);
            }

コードジェネレータを使って文字列から

上の例ではCodeDomで記述された構造をコンパイルしているが、通常のソースコードのように文字列やファイルからコンパイルすることも可能。
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Reflection.Emit;

            var provider = new CSharpCodeProvider();
            var parameters = new CompilerParameters { GenerateInMemory = true };
            // ソースコードに該当する部分を文字列として定義する。
            string code =
@"
            public class Person
            {
                public int Age { get; set; }
                public string Name { get; set; }
            }
";

            var results = provider.CompileAssemblyFromSource(parameters, code);
            //            provider.CompileAssemblyFromFile(parameters,string[])というメソッドもある

            var type = results.CompiledAssembly.GetType("Person");
            var obj = Activator.CreateInstance(type);

IL

ソースコードをコンパイルして生成されるアセンブラはMSILという中間言語で記述される。
C#のソース側からもMSILを記述することは可能。但し、難易度は高いし可読性は悪い。
フレームワーク系のコードなどで偶に見かける。
アセンブリを逆コンパイルした結果を参考にしながらならなんとか書けるかも。
using System;
using System.Reflection;
using System.Reflection.Emit;

        public void SampleCode()
        {
            TypeBuilder tb = GetTypeBuilder();
            CreateProperty(tb, "Age", typeof(int));
            CreateProperty(tb, "Name", typeof(string));
            Type type = tb.CreateType();

            dynamic obj = Activator.CreateInstance(type);
            obj.Age = 20;
            obj.Name = "Taro";

            var obj = Activator.CreateInstance(type);
            type.GetProperty("Age").SetValue(obj, 20, null);
            type.GetProperty("Name").SetValue(obj, "Taro", null);
        }
        private TypeBuilder GetTypeBuilder()
        {
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(Assembly.GetExecutingAssembly().FullName), AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            TypeBuilder tb = moduleBuilder.DefineType("DynamicType",
                                TypeAttributes.Public |
                                TypeAttributes.Class |
                                TypeAttributes.AutoClass |
                                TypeAttributes.AnsiClass |
                                TypeAttributes.BeforeFieldInit |
                                TypeAttributes.AutoLayout,
                                null);
            return tb;
        }

        private void CreateProperty(TypeBuilder tb, string propertyName, Type type)
        {
            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, type, FieldAttributes.Private);
            PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, type, null);
            MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, type, Type.EmptyTypes);
            ILGenerator getIl = getPropMthdBldr.GetILGenerator();
            getIl.Emit(OpCodes.Ldarg_0);
            getIl.Emit(OpCodes.Ldfld, fieldBuilder);
            getIl.Emit(OpCodes.Ret);
            MethodBuilder setPropMthdBldr =
                  tb.DefineMethod("set_" + propertyName,
                    MethodAttributes.Public |
                    MethodAttributes.SpecialName |
                    MethodAttributes.HideBySig,
                    null, new[] { type });
            ILGenerator setIl = setPropMthdBldr.GetILGenerator();
            Label modifyProperty = setIl.DefineLabel();
            Label exitSet = setIl.DefineLabel();
            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.Emit(OpCodes.Stfld, fieldBuilder);
            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);
            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }

posted by RR at 20:10 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2015年04月21日

アクセス修飾子と若干の例外

アクセス修飾子

アクセス修飾子とは、型やそのメンバの公開範囲を指定するもの。
4種とその組み合わせをもつ1種の計5種
public公開どこからでもアクセス可能
protected保護同じクラス内(構造体内)とその派生クラスからのみアクセス可能
internal内部定義されているアセンブリ内からのみアクセス可能
private非公開同じクラス(構造体)内からのみアクセス可能
protected internal保護・内部同一アセンブリ内と他アセンブリでも派生クラスならアクセス可能

インナークラス

インナークラス(内部クラス)から親クラス(外部クラス)のメンバにはアクセス指定に関わらずなんでもアクセス可能。
    class Parent
    {
    // privateを指定
        private int _count;  
        private Inner _inner;

        public Parent()
        {
            _inner = new Inner(this);
        }

        public class Inner
        {
            Parent _parent;

            public Inner(Parent parent)
            {
                _parent = parent;
            }

            public int GetCount()
            {
                // 内部クラスからは外部クラスのprivateにアクセスできる
                return _parent._count; 
            }
        }
    }

フレンドアセンブリ

内部公開(internal)に指定されたクラスは、そのアセンブリ内からのみアクセスが可能であるが、フレンドアセンブリとして明示的な指定がある場合は、そのアセンブリからもアクセス可能となる。
internalに指定されたクラスなどに対し、単体テストコードを定義するアセンブリからのみのアクセスは許容するような使用を想定しているらしい。
ソースコードにこういうコードが入るのは気持ち悪いとか、単体テストの対象は公開(public)にするとか、そもそもinternalではなくてpublicに指定するとか、いろいろ問題とか回避策とかあるのであまり使われない。どうしても必要ならリフレクションあるし。
using System.Runtime.CompilerServices;
// この指定によりTestsuiteSample.dll内に定義されたクラスからはアクセス可能となる
[assembly:InternalsVisibleTo("TestsuiteSample")]
namespace ClassLibrary
{
    // internal指定なので他アセンブリからアクセスは不可
    internal static class Sample
    {
        internal static int MaxCount
        {
            get { return 100; }
        }
    }
}

リフレクション

リフレクションを使えばどのようなアクセス指定のクラスやメンバでもアクセス可能
    public static class Sample
    {
        private static int _count = 100;

        public static int Count
        {
            get { return _count; }
        }
    }
上記のようなクラスがありました。デフォルトカウント値を100から200に変更したかったのですが、setterは定義されてませんし、クラスを変更する権限もありません。そんな場合でも、リフレクションならなんとかなります。
using  System.Reflection;
class Program
{
   static void Tester()
   {
      // _countという静的で非公開なフィールドを取得し
      var field = typeof(ClassLibrary.Sample2).GetField("_count", BindingFlags.Static | BindingFlags.NonPublic);
      // それに値を設定する(第一引数はインスタンスクラスの場合に設定対象のインスタンスを指定する)
      field.SetValue(null, 200);
   }
}

教室事例のようでもありますが、3rd製のライブラリを沢山使ってたり、ファイル管理の規則が面倒な案件だったりするとタマに必要になったりします。
posted by RR at 01:05 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2015年04月26日

継承 どちらが実行されるのか

基底クラスと継承クラスとの関係

基底クラスにあるメンバを継承クラスで上書きするには以下の2通りある。

1.基底側でvirtual指定し継承側でoverride指定する
2.継承側でnew指定する。

実際にどちらが実行されるのかは、以下のとおり。
    // 基底クラス 
    class BaseClass
    {
        public virtual void Method_A()
        {
            Console.WriteLine("BaseClass Method_A");
        }

        public void Method_B()
        {
            Console.WriteLine("BaseClass Method_B");
        }
    }
    // 継承クラス
    class ExtendedClass : BaseClass
    {
        public override void Method_A()
        {
            Console.WriteLine("ExtendedClass Method_A");
        }

        new public void Method_B()
        {
            Console.WriteLine("ExtendedClass Method_B");
        }

    }

    class InheritTest
    {
        static void Main(string[] args)
        {
            BaseClass base1 = new BaseClass();
            base1.Method_A();      //  BaseClass 
            base1.Method_B();      //  BaseClass 

            ExtendedClass extend1 = new ExtendedClass();
            extend1.Method_A();    //  ExtendedClass 
            extend1.Method_B();    //  ExtendedClass 

            // 実行時エラー
            //((ExtendedClass)base1).Method_A(); InvalidCastException
            //((ExtendedClass)base1).Method_B(); InvalidCastException

            // 継承クラスを基底クラスにキャストして実行
            ((BaseClass)extend1).Method_A();  // ExtendedClass 
            ((BaseClass)extend1).Method_B();  // BaseClass

            // 基底クラス型の宣言に継承クラスのインスタンスを代入
            BaseClass base2 = new ExtendedClass();
            base2.Method_A();  // ExtendedClass 
            base2.Method_B();  // BaseClass

            // 参考
            var t = base2.GetType(); // ExtendedClass

            //ExtendedClass extend2 = new BaseClass(); コンパイル不可(CS0266:暗黙的変換エラー)
          
        }

複数のインターフェースに同一のメソッド

多重継承はできないが、インタフェースはいくつでも継承(実現)できる。異なるインタフェースに同一のシグネチャがあった場合に実行されるものは以下のサンプルのとおり。
    class Program
    {
        static void Main(string[] args)
        {
            Sample sample = new Sample();

            string ret1 = sample.GetString(); // Sample
            
            string ret2 = ((ISampleA)sample).GetString(); // ISampleA

            string ret3 = ((ISampleB)sample).GetString(); // ISampleB
        }
    }

    interface ISampleA
    {
        string GetString();
    }

    interface ISampleB
    {
        string GetString();
    }

    public class Sample : ISampleA,ISampleB
    {

        public string GetString()
        {
            return "Sample";
        }

        string ISampleA.GetString()
        {
            return "ISampleA";
        }

        string ISampleB.GetString()
        {
            return "ISampleB";
        }
    }
posted by RR at 23:47 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2015年04月27日

非公開の静的フィールドに定義されたコレクションの中身をリフレクションで書き換える方法

こんなライブラリがありました。プログラム全体で使う定数などを保持しています。
    class Data
    {
        public int ID;
        public string Name;
    }

    public static class DataCollection
    {
        private static List<Data> _list = new List<Data>();

        public static void Add(int id, string name)
        {
            _list.Add(new Data { ID = id, Name = name });
        }

        public static string GetName(int id)
        {
            return _list.Where(m => m.ID == id).Select(m => m.Name).FirstOrDefault();
        }
    }
コードにあるように登録は出来ても変更や削除はできません。しかし、この値を変更する必要がでてきました。

諸般の事情によりこのクラスを改修する権限がないので、しょうがなくリフレクションで値を書き換えました。その方法は以下のとおり。コレクションをIList型にキャストするところがミソでした。


      // フィールドの情報を取得
            var field = typeof(DataCollection).GetField("_list", BindingFlags.Static | BindingFlags.NonPublic);
            // フィールドの値をIList型にキャストする
            var list = field.GetValue(null) as System.Collections.IList;

            // さすればあとはイテレイトできる
            for (int n = 0; n < list.Count; n++)
            {
                var data = list[n];

                var fi = data.GetType().GetField("ID");

                if ((int)(fi.GetValue(data)) == id)
                {
                    data.GetType().GetField("Name").SetValue(data , name);

                    break;
                }
            }
posted by RR at 21:13 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2015年07月01日

WeakReference 弱い参照

WeakReference 弱い参照

メモリ管理は.NET Frameworkお任せであり、使い終わったらガベージコレクタが自動で回収してくれる。
インスタンスが使用中か否かは、そのインスタンスへの参照の有無による。これを強い参照という。
一方、弱い参照とは参照があってもインスタンスがガベージされるうるものをいいます。
インスタンスを生成してからそれを使うまでの間にガベージコレクタが実行されていなければ使えるし、実行されていればNullが返却される。

誤使用例
public class Data
{
    public List<int> List { get; set; }
    public Data(int length)
    {
        List = new List();
        for (int n = 0; n < length; n++) List.Add(n);
    }
}
class Tester
{  
    static void Main(string[] args)
    {
            // 非ジェネリックの弱い参照
            WeakReference wr = new WeakReference(new Data(5));
            // 参照先のインスタンスがガベージされてないかの確認
            if (wr.IsAlive)
            {
                int count2 = ((Data)wr.Target).List.Count;
            }
    }
}
ガベージコレクタの実行契機は.NETお任せです。つまり、上記コードでIsAliveで確認後でwr.Target前にGCが実行される可能性があります。その場合このコードはNullReferenceExceptionがスローされることになります。
これを回避するためには、以下のようにする必要があります。
    var temp = wr.Target;
    if(temp != null)
    {
        int count2 = ((Data)temp).List.Count;
    }

.NET4.5から、WeakReferenceにジェネリクスが追加になりました。
上記のような誤用を避けるためでしょうか、IsAliveプロパティは無くなり、値の取得方法も変わりました。
            List<WeakReference<Entity>> list = new List<WeakReference<Entity>>();

            for (int n = 0; n < 100; n++)
            {
                list.Add(new WeakReference<Entity>(new Entity { ID = n }));
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            Entity entity = null;

             list.ForEach(ent =>
            {
                if (ent.TryGetTarget(out entity))
                {
                    var test = entity;
                }
             });


このWeakReferenceの使いどころとして最初に思いつくのが簡易なキャッシュ機能でしょう。
MSDNのサンプルもキャッシュの実装例を取り上げています。
が、これが使えたもんじゃない。寿命が短すぎるから。
ガベージ(ジェネレーション0のガベージ)が実行されると回収されてしまうのですが、この実行契機は予想外に多い。
メモリが枯渇してきているかに関わらず常に実行され続ける感じです。
パフォーマンスモニタにより、ガベージの実行を実際に観測することができます。
Perfmon.png
図が小さくてちょっと見ずらいですが、画面の中央上部に緑色の十字のアイコンボタンがあります。
これを押下するとモニターする対象を選択できます。
Perfmon2.png
計測したいプロセスを起動させておいて、左上のリストボックスで「.NET CLR Memory」から「#Gen 0 Collections」を選択。
左下のリストボックスで計測対象のプロセス名を選択し、左最下部の「追加」ボタンを押下すれば該当プロセスにおけるGCの実行回数が目視できます。

posted by RR at 00:41 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2015年10月22日

C# メソッドの引数

C#でメソッドを定義する場合は、引数としてどんな型のパラメータをいくつ取るのかを決める必要がある。

引数なし
引数を取らない場合は空のカッコを指定する。
public void Method1()
{
    // DoSometing
}

値渡し
C#でメソッドに引数を指定する場合、基本は値渡しである。
つまり、メソッドの呼び元の値のコピーが引き渡される。
値型の場合はその値そのものコピーが、参照型の場合は参照のコピー(”ポインタの値のコピー”)が渡る。
なので、メソッド内で値を書き換えても、呼び元の値は変わらない。
public void Method1(int val)
{
    val = 10;
}
public void Method2(string val)
{
    val = "method";
}

public void Tester()
{
    // 値型のサンプル
    int value = 0;

    Method1(value);

    int assert1 = value; // 値は0

    // 参照側のサンプル
    string str = "tester";

    Method2(str);

    string assert2 = str; // 値は"tester"
}

配列の例
C#の配列はSystem.Arrayクラスを暗黙に継承した参照型。
値自体、つまりコピーされた参照が変更されても呼び元に影響ないが、
参照が示す先のオブジェクトが変更された場合は、呼び元でも同じアドレスを指しているので、値が変更される。
public void Method1(int[] array)
{
    // 配列への参照(ポインタを書き換え)
    array = new int[]{5,6,7};
}

public void Method2(int[] array)
{
    // 配列の参照が指すオブジェクト自体を変更
    array[0] = 10;
}

public void Tester
{
    int[] array = { 1, 2, 3 };

    Method1(array);

    int[] assert1 = array; // 中身は同じで 1,2,3

    Method2(array);

    int[] assert2 = array: // 0番目が書き変って 10,2,3
}

参照渡し ref / out
基本は値渡しだが、参照(ポインタ)を渡すことも可能。
メソッド定義時にref/outを指定する。
呼び元でも明示的にref/outの指定が必要。
public void Method1(ref int val)
{
    val = 10;
}
public void Method2(out int val)
{
    val = 10; // outパラメタは必ずメソッド内で値を割り当てる必要がある。
}

public void Tester
{
     // ---refのサンプル--------
     // 必ず値を割り当てる必要あり
     int value1 = 0;

     // 呼び元でもref指定
     Method1(ref value1);

     // 値は10
     int assert1 = value1;

     // ---outのサンプル--------
     // 初期化は不要
     int value2; 

     // 呼び元でもout指定
     // メソッドが抜けた時点で値が割り当てられていることが文法上保証される
     Method2(out value2);

     // 値は10
     int assert2 = value2;
}

可変長
引数の型が同じ場合にparamsという予約語を付与することにより可変長の指定が可能。
public void Method(params int[] array)
{
    int sum = array.Sum();
}

public void Tester
{
    // 引数は何個でも
    Method();
    Method(1);
    Method(1,2,3);
}

名前付き
複数の引数をとる場合、順番は記述順に寄るが、明示的に名前を付与することにより順番変えられる。
public void Method(string path,string file)
{
    var full = Path.Combine(path,file);
}

public void Tester
{
    // 引数の順番を指定できる(以下3つは同じ)
    Method("c:\\log","error.log");
    Method(path:"c:\\log",file:"error.log");
    Method(file:"error.log",path:"c:\\log");
}

省略可能
引数にデフォルト値を定義しておくことにより、未指定時にこれを使うように定義することができる。
public void Method(string name,string extension=".txt")
{
    var file = name + extension;
}

public void Tester
{
    // 明示的指定がない場合はデフォルト値が使用される
    Method("error",".log");
    Method("manual");
}

デリゲート
デリゲート、つまり、処理自体も引数に指定することができる。
ActionやFuncといった定義済みデリゲートを使えば簡単に処理を受け渡しできる。
public void Method(Action action)
{
    Console.WriteLine("Start");

    action();

    Console.WriteLine("Finish");
}

public void Tester
{
    // デリゲートも指定できるので処理自体を渡せる
    Method(()=>Console.WriteLine("DoSomething"));
}

ジェネリック
メソッド定義時にメソッドの型を指定するのだけれど、型を呼び元に指定してもらう方法。
メソッドの後ろにwhereで型に条件付与することができる。
// ここでは whereによりTがクラス(つまり参照型)であると限定している
public void Method<T>(T val) where T : class
{
    var hoge = val;
}

public void Tester
{
    // 参照型(クラスなら何でもOK)
    Method("aaa");
  Method(new Exception());
    //Method(10);  // 値型だとコンパイルエラー
}

拡張メソッド
静的クラスに静的メソッドとして定義する。
thisの後に拡張対象のクラスを指定する。
public static class Extensions
{
    // 静的メソッドとして定義し引数にthis指定する
    public static void ExtendedMethod(this string s)
    {
        Console.WriteLine("extended :" + s);
    }
}

public void Tester
{
   "test".ExtendedMethod();
}
posted by RR at 22:26 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2015年10月25日

.NET Framework型とBuild-In型 (Int32とintとかObjectとobject)とか

C#は基本的には.NET Framewrok上で動作するプログラムを記述するための言語であり、この.NET Frameworkが持つ基本データ型に相当するC#独自のBuild-In型が定義されている。

これらは基本的には同じものであり、同等に使用することができる。
      // 同じようなもの。代替も混在も可能 
      int n = Int32.Parse("10");
      Int32 m = int.Parse("20");

一覧

C#の型.NET Frameworkの型説明
boolSystem.Booleantrueまたはfalseのどちらかとなる論理値。既定値はfalse。
byteSystem.Byte0〜255の値を保存する符号なしバイト。既定値は0。
sbyteSystem.SByte-128〜127の値を保存する符号付きバイト。既定値は0。
charSystem.Char16ビットの符号なしUnicode文字。既定値はnull。
decimalSystem.Decimal丸め計算の対象にならない小数。金融計算によく使用される。既定値は0.0m。
doubleSystem.Double倍精度浮動小数点数型。既定値は0.0d。
floatSystem.Single単精度浮動小数点数型。既定値は0.0f。
intSystem.Int3232ビットの符号付き整数型。既定値は0。
uintSystem.UInt3232ビットの符号なし整数型。既定値は0。
longSystem.Int6464ビットの符号付き整数型。既定値は0。
ulongSystem.UInt6464ビットの符号なし整数型。既定値は0。
objectSystem.Objectクラスインスタンスへの参照。既定値はnull。
shortSystem.Int1616ビットの符号付き整数型。既定値は0。
ushortSystem.UInt1616ビットの符号なし整数型。既定値は0。
stringSystem.String文字列オブジェクトへの参照。既定値はnull。
例外
大体は代替可能だが以下の制限がある。

C#6.0以前 列挙体の型指定に.NET Frame型は使えない
    // コロンの後で型を指定できる 
    public enum Signal : int
    {
        Blue,Yellow,Red
    }
    // これはコンパイルが通らない
    //public enum Signal : Int32
    //{
    //    Blue,Yellow,Red
    //}
C#6.0 以降では新規に追加されたnameof演算子で差異がある
    // これはOK
    string a = nameof(Int32);
    // これはコンパイル通らない
    //string b = nameof(int);
posted by RR at 04:04 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2015年11月01日

構造体の初期化

C#6.0以前の構造体の初期化

構造体の初期化の基本
クラスと同様にnew演算子で初期化できる。
new演算子による明示的な初期化を行わなくても使える。
    public struct Shop
    {
        public int ID;
        public string Name;
    }

    class Tester
    {
        static void Execute(string[] args)
        {
            // new演算子による初期化
            Shop shop1 = new Shop();
            shop1.ID = 1;
            shop1.Name = "Shop 1";
            // newなしでも大丈夫
            Shop shop2;
            shop2.ID = 11;
            shop2.Name = "Shop 2";
            // こういうのもあり
            Shop shop3 = new Shop
            {
                ID = 3,
                Name = "Shop 3"
            };
        }
    }
構造体のコンストラクタとフィールド初期化子による初期化
引数なしのコンストラクタは定義できない。
フィールド初期化子も定義できない。
(コンパイルが通らない)
    public struct Shop
    {
        public int ID;
        //public int ID = 0;

        public string Name;
        //public string Name = null;
        
        //public Shop()
        //{
        //    ID = 0;
        //    Name = null;
        //}
    }
構造体と自動プロパティの問題
    public struct Shop
    {
        // フィールドではなく自動プロパティとすると
        public int ID { get; set; }
        public string Name { get; set; }
    }
    class Tester
    {
        static void Execute(string[] args)
        {
            Shop shop1 = new Shop();
            shop1.ID = 1;
            shop1.Name = "Shop 1";
            // これがコンパイルエラーとなるようになる
            Shop shop2;
            //shop2.ID = 11;
            //shop2.Name = "Shop 2";
        }
    }

上で定義してある構造体はコンパイラにより以下のように解釈されているから
逆アセンブリすると以下のようになっている。
    public struct Shop
    {
        // 自動プロパティは以下のように展開される

        // バックストアとよばれる実際に値を保持するプライベートフィールド定義
        // 構造体を使う前にはこのバックストアが初期化されている必要がある
        private int _ID;
        private string _Name;

        public int ID
        {
            get { return _ID; }
            set { _ID = value; }
        }

        public string Name
        {
            get { return _Name; }
            set { _Name = value; }
        }
        // 引数なしのコンストラクタがコンパイラにより自動生成され
        // この中でゼロ初期化というフィールドをデフォルト値で初期化
        // する特殊な初期化処理が行われる
        // フィールド初期化子はコンパイラによりコンストラクタに移行
        // されるが構造体はゼロ初期化用に使っているため移動できない
        // ために定義できない
        public Shop()
        {
            _ID = 0;
            _Name = null;
        }
    }
自動プロパティとコンストラクタ
デフォルトコンストラクタを呼べばよい
    public struct Shop
    {
        public int ID { get; set; }
        public string Name { get; set; }

        // 引数なしコンストラクタは定義できない(コンパイラにより自動生成されるため)
        //public Shop()
        //{
        //    ID = 0;
        //    Name = null;
        //}
     
        // バッキングストアの変数名が解らない
        //public Shop(int id,string name)
        //{
        //    ??= id;
        //    ??= name;
        //}

        // プロパティに値を設定するまえにバックストアを初期化する必要がある
        //public Shop(int id,string name)
        //{
        //    ID= id;
        //    Name= name;
        //}

       // デフォルトコンストラクタを明示的に呼べばよい
        public Shop(int id, string name)
            : this()
        {
            ID = id;
            Name = name;
        }
    }

C#6.0から文法が変更されている

C#6.0の言語仕様はMSDNを探してもみつからない。
プレビュー版はここにありますが、リリース版とは若干異なります。
・デフォルトコンストラクタ(引数なしコンストラクタ)が実装可能になるということだったが採用されなかった。(BCLへの影響が大きすぎて断念したとのこと)
・初期化時にプロパティを初期化する場合に直接バッキングストアに値を設定するよう変更された
public struct Shop
    {
        public int ID { get; set; } 
        public string Name { get; set; }

        // コンパイル通らない
        //public Shop()
        //{ }

        // ※これが可能に変更
        public Shop(int id, string name)
        {
            ID = id;
            Name = name;
        }

    }

新たに採用された自動プロパティ初期化は使えない
    // クラスならこういうのが可能になった
    public class Shop
    {
        public int ID { get; set; } = 10;
        public string Name { get; set; } = "def";
    }
    // 構造体では使えない
    public struct Shop
    {
        // public int ID { get; set; } = 10;
        // public string Name { get; set; } = "def";
    }
posted by RR at 02:17 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする

2016年05月10日

ボックス化とボックス化解除とそのコスト

概要 値型もObject型から派生しているのでObject型に変換することが出来る。値型を参照型であるObject型(またはインタフェース)に変換することをボックス化、ボックス化されているのを値型に戻すことをボックス化解除またはアンボックス化という。

 // ボックス化
 int n = 10;
 object obj = n;

 // アンボックス化
 int m = (int)obj;


このボックス化やボックス化解除はコストが重いらしい。MSDNのこのページから引用。

簡単な代入と比べて、ボックス化およびボックス化解除は負荷の大きいプロセスです。 値型をボックス化するときは、新しいオブジェクトを割り当てて構築する必要があります。 ボックス化ほどではありませんが、ボックス化解除に必要なキャストも大きな負荷がかかります。

同様の記載はいろんな解説ページなどにあるんですが、どの程度影響があるんでしょうか。測ってみます。 ついでにDynamicも合わせて計測します。こんなコードです。

            List<int> list = Enumerable.Range(1, 1000000).ToList();

            var sw = new Stopwatch();
            int sum = 0;
            sw.Start();

            foreach (int val in list)
                sum += val;

            sw.Stop();
            var em1 = sw.ElapsedMilliseconds;
            sum = 0;

            sw.Restart();

            foreach (object val in list) // ここでボックス化
                sum += (int)val;         // ここでボックス化解除

            sw.Stop();
            var em2 = sw.ElapsedMilliseconds;
            sum = 0;

            sw.Restart();

            foreach (dynamic val in list)
                sum += val;

            sw.Stop();
            var em3 = sw.ElapsedMilliseconds;


計測結果は以下のような感じ。
初回   4,20,40(ms)
2回以降 4,10,20(ms)

ボックス化なしに対し、ボックス化&ボックス化解除が介在すると2倍強、Dynamicでは5倍程度遅いです。 但し、100万回づつ廻してますが一桁おとして10万回程度だとほとんど差はなくなります。

これを「パフォーマンスに大きな影響がでる」というのかは使用箇所によるでしょう。数千回ぐらいならほぼ計測誤差ぐらいなので予想ほどでもありませんでした。
posted by RR at 01:01 | Comment(0) | 文法 | このブログの読者になる | 更新情報をチェックする