Vítejte na blog.vyvojar.cz Přihlásit | Registrovat | Pomoc
Titulní Blogy Fotky Soubory

Jakublog

  • Tip: Vynucení implementace rozhraní u argumentu předaného metodě

    Někdy je potřeba vynutit si u argumentu metody určité třídy i rozhraní. Ve frameworku v1.1 se to dalo udělat jen pomocí vyhazování výjimek. U verze 2.0 lze šikovně využít generics.

    Mějme metodu, která přijímá winforms control a zároveň vyžaduje, aby bylo implementováno rozhraní IMyControl.

    Rozhraní a testovací třida:

        public interface IMyControl {
            void DoSomething();
        }

        public class MyButton : System.Windows.Forms.Button, IMyControl {
            public void DoSomething() { this.PerformClick(); }
        }


    Metoda po "starém" způsobu:

        void ProcessMyControl_Old( Control ctl ) {
            if ( ctl == null ) { throw new ArgumentNullException( "ctl" ); }

            IMyControl inf = ctl as IMyControl;
            if ( inf == null ) { throw new ArgumentException( "Can not cast to 'IMyControl' interface.", "ctl" ); }

            inf.DoSomething();
        }


    Metoda po "novém" způsobu:

        void ProcessMyControl_New<T>( T ctl ) where T : Control, IMyControl {
            if ( ctl == null ) { throw new ArgumentNullException( "ctl" ); }

            ctl.DoSomething();
        }


    Volání metody:

        private void button2_Click( object sender, EventArgs e ) {
            Button btnStandard = new Button(); // klasicke tlacitko
            MyButton btnExtended = new MyButton(); // tlacitko s implementaci rozhrani IMyControl

            ProcessMyControl_Old( btnStandard ); // kompilator povoli, a za behu se vyhodi vyjimka
            ProcessMyControl_Old( btnExtended ); // ok

            ProcessMyControl_New( btnStandard ); // kompilator nepovoli
            ProcessMyControl_New( btnExtended ); // ok
        }
  • ThreadStatic fields

    Nedávno jsem narazil na třídu System.ThreadStaticAttribute. Jeho funkcionalita je opravdu skvělá. Zařizuje pro každé vlákno různou hodnotu. Takový jednoduchý příklad - zkuste si pustit následující kód s a bez ThreadStatic atributu:
    class AAA {
        static void Start() {
            System.Threading.Thread thread_1 = new System.Threading.Thread( new System.Threading.ParameterizedThreadStart( Worker ) );
            System.Threading.Thread thread_2 = new System.Threading.Thread( new System.Threading.ParameterizedThreadStart( Worker ) );
    
            thread_1.Start( "ahoj" );
            thread_2.Start( "baf" );
        }
    
        static void Worker( object obj ) {
            FlyWeightClass fly = FlyWeightClass.Default;
    
            fly.SomeText = obj != null ? obj.ToString() : "( null )";
            for ( int i = 0; i < 100; i++ ) {
                Console.WriteLine( fly.SomeText );
                System.Threading.Thread.Sleep( 100 );
            }
        }
    }
    
    class FlyWeightClass {
        [ThreadStatic]
        private static FlyWeightClass threadSingelton;
    
        public static FlyWeightClass Default {
            get {
                FlyWeightClass ret = FlyWeightClass.threadSingelton;
                if ( ret == null ) { FlyWeightClass.threadSingelton = ret = new FlyWeightClass(); }
    
                return ret;
            }
        }
    
        public string SomeText;
    }
    
    Důležitá poznámka je, že v takovýchto případech je potřeba vyhnout se inicializaci statické proměné v deklaraci. Taková inicializace se provede pouze jednou, a při přístupu z jiného vlákna tam pak budete mít výchozí hodnotu.
  • Rozdíl mezi "readonly int" a "const int".

    Určitě jste někdy v aplikaci potřebovali mít nějakou hodnotu konstantní, ale nemohli jste použít klíčové slovo "const", protože se nejednalo o základní datový typ. Určitě se trefím, pokud budu hádat, že jste použili klíčové slovo "readonly" na nějaký field, pojmenovali ho stejně jako by to byla konstanta, a dále tak s ní pracovali.

    Mějme následující třídu:

    public class Class1 {
      public const int ConstThree = 3;
      public readonly int ReadOnlyThree = 3;
      public readonly System.Drawing.Color ReadOnlyBlue = System.Drawing.Color.Blue;

      public int GetConstThree() {
        return ConstThree;
      }
      public int GetReadOnlyThree() {
        return this.ReadOnlyThree;
      }
      public System.Drawing.Color GetReadOnlyBlue() {
        return this.ReadOnlyBlue;
      }
    }

    Tu použijeme v jednoduché metodě:

    void DoSomething(){
      Class1 cls = new Class1();
      ChangeValues(cls);

      string str = cls.GetConstThree.ToString() + ", " + Class1.GetReadOnlyThree.ToString();
      Concole.WriteLine(str);
      str = cls.GetReadOnlyBlue().Name;
      Console.WriteLine(str);
    }

    Pokud předpokládáte, že výstupem bude "3, 3" a "Blue", tak se mýlite. Výstupem může kupodivu být "3, X" a "Y", kde X je jakýkoliv int a Y jakýkoliv název barvy.
    Jestli Vás to překvapuje, tak Vás vítám v klubu :). Kouzelný kód v metodě ChangeValues() je následující:

    void ChangeValues(Class1 cls){
      System.Reflection.FieldInfo field = cls.GetType().GetField( "ReadOnlyThree" );
      field.SetValue( cls, 9 );

      field = cls.GetType().GetField( "ReadOnlyBlue" );
      field.SetValue( cls, System.Drawing.Color.Red );
    }

    Z nějakého důvodu lze readonly fieldům přiřazovat hodnotu skrz reflection. Osobně to považuji za bug, neboť ve specifikaci k c#2.0, psáno (oddíl 17.4.2):

    When a field-declaration includes a redonly modifier, the fields introduced by the declaration are . Direct assignments to readonly fields can only occur as part of that declaration or in an instance constructor or static constructor in the same class. (A readonly field can be assigner to multiple times in thes contexts.) Specifically, direct assignments to a readonly field are permitted only in the following contexts:
    • In the variable-declarator that introduces the field (by including a variable-initializer in the declaration).
    • For an instance field, in the instance constructors of the class that contains the field declaration; for a static field in the static constructor of the class that contains the field declaration. These are also the only contexts in which it is valid to pass a readonly field as on "out" or "ref" parameter.
    (Dále následuje ukázka jak lze "šikovně" využít readonly fieldy pro konstanty)

    Stejné chování je u .net 2.0 i 1.1. Teď je jen otázkou, zda to je bug v .netu nebo ve specifikaci :).

    Edit: takže to neni bug, ale design :) - viz. níže
  • Hrátky s vlastnostmi II ( ASP.NET )

    Pokračování minulého příspěvku - využití "property" třídy v prvcích ASP.NET.

    Následující kód je jednoduchá předělávka třídy Property<T> do prostředí ASP.NET, kde se stav objektu uchovává ve ViewState.

    public class WebProperty<T> : IWebProperty {
        #region Constants
        const string CACHE_KEY = "WebProperty.InitWebProperties.Cache";
        #endregion

        #region Fields
        private StateBag viewState;
        private string propertyKey;
        #endregion

        #region Properties
        public T Value {
        get {
                if ( this.viewState == null ){ throw new NullReferenceException("ViewState can not be null."); }
                object ret = this.viewState[this.propertyKey];
                if ( ret == null ) {
                    return default( T );
                }
                return (T)ret;
            }
            set { this.viewState[this.propertyKey] = value; }
        }
        object IWebProperty.Value {
            get { return this.Value; }
            set { this.Value = (T)value; }
        }
        public StateBag ViewState{
            get{ return this.viewState; }
            set{ this.viewState = value; }
        }
        #endregion

        #region Constructors
        public WebProperty(string propertyKey) : this(propertyKey, null) {
        }
        public WebProperty(string propertyKey, StateBag viewState){
            if ( propertyKey == null ) { throw new ArgumentNullException( "propertyKey" ); }
            if ( propertyKey.Length == 0 ) { throw new ArgumentException( "Parameter 'propertyKey' must be longer than 0.", "propertyKey" ); }

            this.propertyKey = propertyKey;
            this.viewState = viewState;
        }
        #endregion

        #region Static methods
        // ... potrebne staticke metody ukazu nize
        #endregion
        }

    V UserControlu pak takovouto vlastnost muzeme nadefinovat takto:

    public partial class WebUserControl : System.Web.UI.UserControl{
        protected readonly WebProperty<string> NejakyText;
        public WebUserControl() {
            WebProperty<object>.InitWebProperties( this, this.ViewState, true ); // toto volani bude za chvili vysvetleno
        }
    }

    Protoze se kazda instance tridy WebProperty<T> musí odvolávat do ViewState nějakým klíčem, a nikdo jistě nechce tyto klíče vypisovat ručně ( alespoň já ne :) ), tak třída WebProperty<T> obsahuje statickou metodu InitWebProperties(), která nainicializuje tyto proměnné/vlastnosti za nás.
    Předává se jí instance, která proměnné obsahuje, instance třídy StateBag, která bude použita pro načítání/ukládání hodnot a nakonec přepínač, zda si informace pro inicializaci proměných tohoto typu třídy má uložit do cache.

    Kód, pro statických metod třídy WebProperty<T>:

        private static FieldInfo[] GetPropertyFields( Type componentType, bool useCache ) {
        object obj = HttpContext.Current.Cache[CACHE_KEY];

        Dictionary<Type, FieldInfo[]> cache;
            if ( obj is Dictionary<Type, FieldInfo[]> ) {
                cache = (Dictionary<Type, FieldInfo[]>)obj;
            }
            else {
                cache = new Dictionary<Type, FieldInfo[]>();
            }

            FieldInfo[] fields;
            if ( useCache == false || cache.ContainsKey( componentType ) ) {
                fields = (FieldInfo[])cache[componentType];
            }
            else {
                Type basePropertyType = typeof( IWebProperty );
                string strBasePropertyType = basePropertyType.FullName;

                FieldInfo[] tmpFields = componentType.GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
                List<FieldInfo> lst = new List<FieldInfo>();
                foreach ( FieldInfo field in tmpFields ) {
                    Type fieldType = field.FieldType;
                    Type tmpType = fieldType.GetInterface( strBasePropertyType, false );
                    if ( tmpType == basePropertyType ) {
                        lst.Add( field );
                    }
                }

                fields = lst.ToArray();
            }

            if ( useCache ) {
                cache[componentType] = fields;
                HttpContext.Current.Cache[CACHE_KEY] = cache;
            }

            return fields;
        }
        public static void InitWebProperties( object instance, StateBag viewState ) {
            InitWebProperties( instance, viewState, false );
        }
        public static void InitWebProperties( object instance, StateBag viewState, bool useCache ) {
            if ( viewState == null ) { throw new ArgumentNullException( "viewState" ); }

            Type mainType = instance.GetType();

            FieldInfo[] fields = GetPropertyFields( mainType, useCache );

            foreach ( FieldInfo field in fields ) {
                string strID = "WebProperty_{0}" + field.Name;

                IWebProperty property;
                try {
                    property = (IWebProperty)Activator.CreateInstance( field.FieldType, strID );
                }
                catch ( Exception exc ) {
                    string msg = string.Format("Type '{0}' can not be created.", strID);
                    throw new TypeLoadException( msg, exc );             }
                property.ViewState = viewState;

                field.SetValue( instance, property );
            }
        }


    Poslední ( třetí ) část bude o použití "property" třídy ve WinForms + automatická podpora práce s PropertiesWindow ( to se pravděpodobně bude týkat i asp.net ).
  • Hrátky s vlastnostmi

    Taky Vás nebaví pro každou vlastnost definovat proměnou, accesory ( get & set metody v property ), události před a po změně hodnoty + metody pro spuštění událostí?

    Nedávno jsem vytvářel takovýchto členů větší množství a nezbylo mi, než si na to napsat generátor kódu. Leč, i po dodělané práci mé línějši Já donutilo mé pracovitější Já popřemýšlet, jak si v budoucnu ulehčit práci.

    Při psaní mnoha takovýchto členu se autor může jednoduše upsat či snadno vytvoří bug při pužití ctrl+c, ctrl+v. A vůbec tak vzniká spousta kódu, který je prakticky stejný, pouze se váže na jiný člen třídy. Takový kód by se přece ve správně navržené oop aplikaci nemel objevovat :).

    Nakonec jsem asi na řešení přišel.
    Nadefinoval jsem si třídu, která poskytuje funkčnost, jakou očekáváme od klasických vlastností.

    public class Property<T> {
        private T value;

        public virtual T Value {
            get { return this.value; }
            set {
                CancelEventArgs ce = new CancelEventArgs(false);
                this.OnChanging(ce);
                if (ce.Cancel == false) {
                    this.value = value;
                    this.OnChanged(EventArgs.Empty);
                }
            }
        }

        public Property() {
        }

        public Property(T initialValue) {
            this.Value = initialValue;
        }

        private void OnChanging(CancelEventArgs e) {
            CancelEventHandler ev = this.Changing;
            if (ev != null) { ev(this, e); }
        }
        private void OnChanged(EventArgs e) {
            EventHandler ev = this.Changed;
            if (ev != null) { ev(this, e); }
        }

        public event CancelEventHandler Changing;
        public event EventHandler Changed;
    }

    Při použití si pak můžete ušetřit spoustu práce. Obzvlášť, pokud rádi všechny členy škaltujete po druzích jako já :).

    // definovane vlastnost po "novu"
    public class NewClass{
        #region Fields ( properties )
        public readonly Property<int> SomeNumber = new Property<int>(1);
        public readonly Property<string> SomeText = new Property<string>("default text");
        // ... dalsi promenne-vlastnosti
        #endregion
    }

    // definovane vlastnosti po "staru"
    public class OldClass{
        #region Fields
        private int someNumber = 1;
        // ... dalsi promenne
        #endregion

        #region Properties
        public int SomeNumber{
        get{ return this.someNumber; }
        set{
                CancelEventArgs ce = new CancelEventArgs(false);
                this.OnSomeNumberChanged(ce);
                if ( ce.Cancel == false ){
                    this.someNumber = value;
                    this.OnSomeNumberChanged(EventArgs.Empty);
                }
            }
        }
        // ... dalsi vlastnosti
        #endregion

        #region Triggers
        protected void OnSomeNumberChanging(CancelEventArgs e){
            CancelEventHandler ev = this.SomeNumberChanging;
            if ( ev != null ){ ev(this, e); }
        }
        protected void OnSomeNumberChanged(EventArgs e){
        EventHandler ev = this.SomeNumberChanged;
        if ( ev != null ){ ev(this, e); }
        }
        #endregion

        #region Events
        public CancelEventHandler SomeNumberChanging;
        public EventHandler SomeNumberChanged;
        #endregion
    }

    Použití takové vlastnosti je pak téměř stejné.

    void DoSomething(){
        NewClass n = new NewClass();
        n.SomeNumber.Changed += new EventHandler(n_SomeNumber_Changed);
        n.SomeNumber.Value = 4;

        OldClass o = new OldClass();
        o.SomeNumberChanged += new EventHandler(o_SomeNumberChanged);
        o.SomeNumber = 4;
    }

    void n_SomeNumber_Changed(object sender, EventArgs e){
        Console.WriteLine("n.SomeNumber was changed");
    }
    void o_SomeNumberChanged(object sender, EventArgs e){
        Console.WriteLine("o_SomeNumberChanged was changed");
    }

    Toto řešení se mi docela líbí. :). Až na nutnost psát o ".Value" navíc přináší samé plusy ( alespoň já to tak vidím ).

    V příštím příspěvku ukážu jak si stejným způsobem ušetřit práci s vlastnostmi u controlů a stránek v ASP.NET. A až doválčím s winforms designerem, tak i potomka třídy Property<T>, která splňuje požadavky pro plně funkční a pohodlné použití u winforms prvků.

    Tak teď tvrdě do mě, dejte mi vědět co se Vám na tom líbí a nelíbí.
Powered by Community Server (Personal Edition), by Telligent Systems
Vyvojar.cz na prodej!