Sunteți pe pagina 1din 32

Atribute si interfete

Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

1
21.01.2007

Curs 3
Atribute. Interfete.
Atribute
Ideea de baza: autodescrierea componentelor. Astfel compilatorul si CLR pot citi aceste
informatii. De asemenea dezvoltatorul componentei are toate informatiile despre componenta
intr-un singur loc.
Atributele ne furnizeaza informatiile asociate cu tipul definit in C#. Important e ca aceasta
informatie poate fi definita de dezvoltatorul tipului si nu este statica, legata de limbaj.
Aceasta asociere de informatie urmeaza aceleasi principii folosite in dezvoltarea XML.
Putem crea un atribut bazat pe orice informatie dorim. Exista un mecanism standard pentru
definirea atributelor si pentru interogarile membrului sau tipului la run time.
Toate atributele, preconstruite sau definite de utilizator, sunt derivate direct sau indirect din
System.Attribute.
Atributele mostenesc o anumita comportare implicita:
atributul poate fi asociat cu orice element tinta;
poate fi sau nu poate fi mostenit de un element derivat;
instante multiple pot fi permise sau nu pe acelasi element tinta.
Aceste comportari sunt specificate in AttributeUsageAttribute.
Un atribut este o adnotare ce poate fi plasata pe un element al codului sursa si este folosit
pentru a memora in momentul compilarii, informatii specifice aplicatiei. Aceasta informatie
este memorata in metadata si poate fi accesata fie in timpul executiei aplicatiei, prin procesul
cunoscut sub numele de reflexie sau cand un alt utilitar citeste metadata. Atributele pot
schimba comportarea aplicatiei in momentul executiei.
CLI predefineste anumite tipuri de atribute si le foloseste pentru a controla comportarea la
runtime.
Putem crea propriile noastre clase de atribute, mostenite din Attribute. Definitia unei
asemenea clase include numele atributului, comportarea sa implicita si alte informatii.
Exemplu
Urmatorul exemplu creaza si atribuie atribute definite de utilizator unei anumite clase.
Atributul contine numele programatorului si versiunea clasei.
using System;
[AttributeUsage(AttributeTargets.Class|
AttributeTargets.Struct,
AllowMultiple=true)]
public class Author : Attribute
{
string authorName;
public double verSion;
public Author(string name)
{
authorName = name;
verSion = 1.0;
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

2
21.01.2007

public string getName()


{
return authorName;
}
}
[Author("Some Author")]
class FirstClass
{
/*...*/
}
class SecondClass
{
/*...*/
}

// nu are atributul Author

[Author("Some Author"),
Author("Some Other Author", verSion=1.1)]
class ThirdClass
{
/*...*/
}
class AuthorInfo
{
public static void Main()
{
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}
public static void PrintAuthorInfo(Type type)
{
Console.WriteLine("Author information for {0}",
type);
Attribute[] attributeArray =
Attribute.GetCustomAttributes(type);
foreach(Attribute attrib in attributeArray)
{
if (attrib is Author)
{
Author author = (Author)attrib;
Console.WriteLine("
{0}, version {1:f}",
author.getName(),
author.verSion);
}
}
Console.WriteLine();
}
}

Iesirea este:
Author information for FirstClass
Some Author, version 1.00
Author information for SecondClass
Author information for ThirdClass
Some Author, version 1.00
Some Other Author, version 1.10

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

3
21.01.2007

Definirea atributelor
Un atribut este in momentul de fata o clasa derivata din System.Attribute.
Sintaxa folosita pentru a atasa un atribut la un tip sau membru este asemanatoare cu cea
folosita la instantierea unei clase.
Interogarea atributelor
Pentru a interoga un tip sau un membru despre atributele atasate acestuia, trebuie sa utilizam
reflexia (reflection).
Reflection ne permite de a determina in mod dinamic in momentul executiei caracteristicile
unei aplicatii.
Putem folosi reflection pentru a citi metadata pentru un intreg assembly si a produce o lista a
tuturor claselor, tipurilor si metodelor definite pentru acel assembly.

Atribute la nivel de clasa


Exemplu
Presupunem ca dorim sa definim un atribut ce va defini calculatorul pe care va fi creat un
obiect. Daca nu folosim atribute, atunci trebuie sa salvam aceasta informatie intr-o constanta
sau un fisier de resurse al aplicatiei.
Cu ajutorul atributelor, adnotam clasa cu calculatorul pe care vom crea obiectele astfel :
public enum RemoteServers
{
JEANVALJEAN,
JAVERT,
COSETTE
}
public class RemoteObjectAttribute : Attribute
{
public RemoteObjectAttribute(RemoteServers Server)
{
this.server = Server;
}
protected RemoteServers server;
public string Server
{
get
{
return RemoteServers.GetName(
typeof(RemoteServers), this.server);
/*
Daca folosim:
return RemoteServers.GetName(
typeof(RemoteServers), 1 );
rezultatul este JAVERT
Vezi Enum in .NET FCL (Framework Class Library).

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

4
21.01.2007

*/
}
}
}
[RemoteObject(RemoteServers.COSETTE)]
class MyRemotableClass
{
}

Pentru a determina serverul pe care se creaza obiectul putem folosi urmatorul cod (explicatiile
sunt comentate in cod):
class Test
{
[STAThread]
static void Main(string[] args)
{
//
// Obtinem tipul obiectului asociat cu MyRemotableClass
//
Type type = typeof(MyRemotableClass);
//
// Dupa ce am obtinut obiectul, putem incepe interogarea lui
//
/*
GetCustomAttributes returneaza un vector ale carui elemente sunt
atributele obiectului. Parametrul true specifica daca cautarea
se face si pentru membrii mosteniti. Din cauza ca nu avem
mostenire in acest caz, putem folosi true/false cu acelasi
efect.
*/
foreach (Attribute attr in
type.GetCustomAttributes(true))
{
/*
Se incearca conversia atributului la
RemoteObjectAttribute.
Daca conversia nu reuseste atunci
remoteAttr = null;
*/
RemoteObjectAttribute remoteAttr =
attr as RemoteObjectAttribute;
if (null != remoteAttr)
{
Console.WriteLine(
"Create this object on {0}.",
remoteAttr.Server);
}
}
}
}

Rezultatul este:
Create this object on COSETTE.

Pentru a defini mai multe atribute la o clasa vezi si exemplul din pagina web.

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

5
21.01.2007

Atribute la nivel de metoda


Codul pentru interogarea atributelor unei metode este diferit de cel folosit la interogarea
atributelor unei clase.
Explicam acest lucru pe baza unui exemplu.
namespace AtributeMetode
{
public class AtributTranzactie : Attribute
{
public AtributTranzactie()
{
Console.WriteLine(Ctor AtributTranzactie);
}
}
class Atx
{
[AtributTranzactie]
public void Txt1()
{}
public void NonTxt()
{}

[AtributTranzactie]
public void Txt2()
{}

class Test
{
[STAThread]
static void Main(string[] args)
{
// Clasa se califica cu numele spatiului
// pentru ca am definit un namespace
Type type = Type.GetType("AtributeMetode.Atx");
foreach (MethodInfo method in type.GetMethods())
{
foreach (Attribute attr in
method.GetCustomAttributes(true))
{
if (attr is AtributTranzactie)
{
Console.WriteLine(
"Metoda
{0} are atributul AtributTranzactie.",
method.Name);
}
}
}
}
}
}

Iesirea este:
AtributTranzactie ctor

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

6
21.01.2007

Metoda Txt1 are atributul AtributTranzactie.


AtributTranzactie ctor
Metoda Txt2 are atributul AtributTranzactie.
Observatie:
Cand atasam un atribut cu un constructor fara parametri, nu mai este nevoie de paranteze.
Cateva explicatii:
Type.GetMethods = regaseste un vector ce are elemente obiecte de tip MethodInfo.
Fiecare element contine informatii despre o metoda din clasa Atx.
MethodInfo.GetCustomAttributes = regaseste toate atributele create de utilizator pentru o
metoda.
Pentru a testa daca metoda are atributul AtributTranzactie folosim operatorul is.
Exemplu 2
Se ataseaza atributul Serializable (atribut preconstruit) in vederea serializarii datelor unei
clase. Vezi codul ce urmeaza (ex. din internet, valabil pentru .NET 2.0).
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;
[Serializable]
public class User
{
public string userID;
public string password;
public string email;
public string city;
public void Save(string fileName)
{
FileStream s=new FileStream(fileName,FileMode.Create);
SoapFormatter sf=new SoapFormatter();
sf.Serialize(s,this);
}
static void Main(string[] args)
{
User u=new User();
u.userID="firstName";
u.password="Zxfd12Qs";
u.email="asdf@qwer.com";
u.city="TheCity";
u.Save("user.txt");
}
}

Atribute pentru campuri (field attributes)


Vom explica atributele pentru campuri pe baza unui exemplu, in care incercam sa validam
lungimea unor siruri de caractere (user, password, email).
user si password : cu lungimea intre 4 si 8 caractere ;
email : cu lungimea intre 4 si 60 caractere ;
Dorim validarea acestor campuri inainte de serializare. In caz ca cel putin un camp este
invalid se abandoneaza operatia de serializare.

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

7
21.01.2007

Definim un atribut ValidLength (clasa este ValidLengthAttribute), ce accepta doi parametri


pozitionali, lungimea minima si lungimea maxima, si un parametru optional ce va contine
mesajul afisat utilizatorului.
Daca nu se specifica o valoare pentru parametrul optional se va afisa un mesaj implicit.
using
using
using
using
using

System;
System.IO;
System.Runtime.Serialization.Formatters.Soap;
System.Reflection;
System.Collections;

[Serializable]
public class User
{
[ValidLength(4,8,Message=
"UserID should be between 4 and 8 characters long")]
public string userID;
[ValidLength(4,8,Message=
"Password should be between 4 and 8 characters long")]
public string password;
[ValidLength(4,60)]
public string email;
public string city;
public void Save(string fileName)
{
FileStream s=new FileStream(fileName,FileMode.Create);
SoapFormatter sf=new SoapFormatter();
sf.Serialize(s,this);
}
static void Main(string[] args)
{
User u=new User();
u.userID="first";
u.password="Zxfd12Qs";
u.email=".com";
u.city="";
Validator v=new Validator();
if(!v.IsValid(u))
{
foreach(string message in v.Messages) Console.WriteLine(message); }
else {u.Save("user.txt");} }
}

Pentru a valida obiectul, apelam metoda IsValid a obiectului Validator.


Clasa Validator poate fi acum utilizata pentru a valida un obiect al oricarei clase apeland
metosa IsValid.
Clasa pentru definirea atributului are urmatoarea descriere:
[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)]
public class ValidLengthAttribute : Attribute
{
private int _min;
private int _max;
private string _message;
public ValidLengthAttribute(int min,int max)
{

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

8
21.01.2007

_min=min;
_max=max;

}
public string Message
{
get {return(_message);}
set {_message=value;}
}
public string Min
{
get{return _min.ToString();}
}
public string Max
{
get{return _max.ToString();}
}
public bool IsValid(string theValue)
{
int length=theValue.Length;
if(length >= _min && length <= _max) return true;
return false;
}
}

iar clasa Validator arata astfel:


public class Validator
{
public ArrayList Messages=new ArrayList();
public bool IsValid(object anObject)
{
bool isValid=true;
FieldInfo[] fields =
anObject.GetType().GetFields(BindingFlags.Public|BindingFlags.Instance);
foreach (FieldInfo field in fields)
if(!isValidField(field,anObject)) isValid=false;
return isValid;
}
private bool isValidField(FieldInfo aField,object anObject)
{
object[] attributes=
aField.GetCustomAttributes(typeof(ValidLengthAttribute),true);
if(attributes.GetLength(0) ==0) return true;
return isValidField(aField,anObject,
(ValidLengthAttribute)attributes[0]);
}
private bool isValidField(FieldInfo aField,
object anObject,ValidLengthAttribute anAttr)
{
string theValue=(string)aField.GetValue(anObject);
if (anAttr.IsValid(theValue))
return true;
addMessages(aField,anAttr);
return false;
}
private void addMessages(FieldInfo aField,ValidLengthAttribute anAttr)
{
if(anAttr.Message !=null)
{
Messages.Add(anAttr.Message); return;
}
Messages.Add("Invalid range for "+ aField.Name+
". Valid range is between " + anAttr.Min +" and " + anAttr.Max);

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

9
21.01.2007

Clasa Validator foloseste clasele reflection pentru a valida obiectul pasat ca parametru
metodei IsValid.
1. Se extrag toate campurile publice din obiect folosind metoda
GetType().GetFields(BindingFlags.Public|BindingFlags.Instance).
Pentru fiecare camp, se extrage atributul definit de utilizator de tip ValidLengthAttribute
folosind metoda GetCustomAttributes(typeof(ValidLengthAttribute),true).
Daca nu se gaseste atributul nostru pentru un camp, metoda presupune campul ca fiind valid.
Daca se gaseste atributul nostru pentru acel camp, se apeleaza metoda IsValid din
ValidLengthAttribute pentru a valida valoarea campului.

Parametrii pozitionali si parametri cu nume


In exemplele anterioare, am examinat atasarea atributelor via constructorul lor.
Vom examina cateva probleme legate de constructorul atributelor.
In exemplul pentru atributele campurilor am folosit un atribut cu numele FieldAttribute.
Constructorul acestuia arata astfel:
public FieldAttribute(Informatica an, String nume)

Folosind signatura constructorului, am atasat atributul la camp astfel:


[FieldAttribute(Informatica.AN_4, "Popescu")]
public int nCuAtribut;

Putem folosi valori implicite folosind parametri pozitionali si parametri cu nume.


Parametrii pozitionali sunt parametrii din constructorul atributului.
Acestia trebuiesc specificati de fiecare data cand folosim atributul.
Parametrii cu nume nu sunt definiti in constructorul atributului, acestia sunt campuri
nestatice si proprietati (am folosit in primul exemplu din acest curs).
Parametrii cu nume permit clientului sa seteze atributul campurilor si proprietatilor cand
atributul este instantiat, fara a avea nevoie sa cream un constructor pentru fiecare combinatie
posibila de campuri si proprietati.
Fiecare ctor public poate defini un sir de parametri pozitionali.
Sa vedem etapele pentru a folosi parametri cu nume.
Utilizatorul poate referentia ca parametru cu nume orice camp care nu este readonly, static
sau const sau orice proprietate ce include un accesor set setter care nu este statica.

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

10
21.01.2007

Pentru a face FieldAttribute.Nume un parametru cu nume, il vom sterge din ctor pentru ca el
exista deja ca o proprietate read/write:
// public FieldAttribute(Informatica an, String nume)
public FieldAttribut(string nume)
{
// this.an = an;
this.nume = nume;
Console.WriteLine("Ctor FieldAttribute");
}
Utilizatorul poate atasa atributul in doua moduri:
[FieldAttribute("Popescu")]
public int nCuAtribut;
sau
[FieldAttribute(Popescu,An = Informatica.AN_4)]
public int nCuAtribut;

iar rezultatul va fi acelasi.


O problema apare aici. Care este valoarea implicita pentru FieldAttribute.An?
In caul de fata An este un enum si deci de tip int, ca atare compilatorul va initializa cu 0.
Dar AN_1 este 0, deci am putea crede ca l-am initializat noi. Trucul de programare consta in a
scrie enum astfel:
public enum Informatica
{
AN_1 = 1,
AN_2,
AN_3,
AN_4,
POSTUNIVERSITARE
}

astfel ca putem identifica daca An a fost initializat de compilator sau de utilizator.


In ctor va trebui sa adaugam cod pentru a seta An pe o valoare corecta scopurilor noastre.
Acest cod poate fi:
public FieldAttribue(String nume)
{
if (this.An == 0)
this.An = Informatica.AN_4;
this.nume = nume;
}

Vezi exemplul complet in pagina.


Greseli cu parametrii cu nume

Cand folosim parametri cu nume, trebuie sa specificam parametri pozitionali mai intai, si apoi
parametri cu nume in orice ordine dorim.
Compilatorul incearca sa rezolve mai intai parametrii cu nume si apoi ce ramine, incearca sa
gaseasca un ctor cu signatura potrivita.

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

11
21.01.2007

Parametrii cu nume pot fi orice camp sau proprietate accesibila publica incluzind o
metoda setter care nu este statica sau constanta.
Tipurile parametrului attribut
Tipurile parametrilor pozitionali si ai celor cu nume pentru o clasa atribut sunt limitate la
tipurile parametrului atributului, si care poate fi:
bool, byte, char, double, float, int, long, short, string
System.Type
object
tipul enum cu conditia ca oriunde se gaseste definit sa fie accesibil in mod public.
un tablou unidimensional ce are ca tipuri de elemente unul din cele de mai sus.

Atributul AttributeUsage
AttributeUsage defineste modul cum atributele sunt folosite.
Sintaxa este:
[AttributeUsage(
validon,
AllowMultiple = allowmultiple,
Inherited = inherited
)]
Observam un parametru pozitional si doi cu nume.

Parametrul validon
Parametrul validon este de tipul AttributeTarget, ne permite sa specificam tipurile pe
care atributul poate fi atasat.
public enum AttributeTargets
{
Assembly
= 0x0001,
Module
= 0x0002,
Class
= 0x0004,
Struct
= 0x0008,
Enum
= 0x0010,
Constructor = 0x0020,
Method
= 0x0040,
Property
= 0x0080,
Field
= 0x0100,
Event
= 0x0200,
Interface
= 0x0400,
Parameter
= 0x0800,
Delegate
= 0x1000,
All = Assembly Module Class Struct Enum Constructor
Method Property Field Event Interface Parameter
Delegate,
ClassMembers = Class Struct Enum Constructor Method
Property Field Event Delegate Interface,
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

12
21.01.2007

Implicit este AttributeTarget.All.


Membrii din enumerare pot fi folositi cu operatorul OR (|) ca in exemplul:
[AttributeUsage(AttributeTargets.Field AttributeTargets.Property)]

Folosim acest parametru cand vrem sa controlam exact modul de utilizare al unui atribut. In
exemplelel anterioare am folosit atribute pentru clase, metode, campuri.
Pentru a fi siguri ca atributele pe care le-am scris sunt folosite pentru tipurile pentru care au
fost proiectate, putem indica in definitia atributului pentru ce este proiectat:
[AttributeUsage(AttributeTargets.Class)]
public class RemoteObjectAttribute : Attribute
{}
[AttributeUsage(AttributeTargets.Method)]
public class AtributTranzactie : Attribute
{}
[AttributeUsage(AttributeTargets.Field)]
public class FieldAttribute : Attribute
{}
[RemoteObject(RemoteServers.COSETTE)]
class SomeClass
{
[AtributTranzactie]
public void Foo() {}

[FieldAttribute("Bar", An = Informatica.AN_4)]
public int Bar;

Parametrul AllowMultiple
Ne permite sa definim un atribut ce poate fi folosit o singura data pentru un camp sau de mai
multe ori.
Implicit toate atributele sunt folosite ca single, deci nu putem declara de doua ori acelasi
atribut pentru un camp. Vezi exemplul:
public class SomethingAttribute : Attribute
{
public SomethingAttribute(String str){}
}
// Error: "Duplicate single-use Something attribute"
[Something("abc")]
[Something("def")]
class MyClass{}

Parametru AllowMultiple rezolva aceasta problema.


AllowMultiple = true atribut multiplicat;
= false atribut single.
Vezi si exemplul:

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

13
21.01.2007

[AttributeUsage(AttributeTargets.All, AllowMultiple=true)]
public class SomethingAttribute : Attribute
{
public SomethingAttribute(String str){}
}
[Something("abc")]
[Something("def")]
class MyClass{}

Parametrul Inherited
Acest parametru ne spune daca atributul poate fi mostenit sau nu. Valoarea implicita este
false.
Parametrii AllowMultiple si Inherited trebuiesc considerati impreuna. Vezi tabelul de mai jos.
Inherited AllowMultiple Result
Atributul derivat suprascrie atributul
true
false
de baza.
Atributele derivate si cele din clasa de
true
true
baza sunt combinate.
De exemplu:
using System;
using System.Reflection;
namespace AttribInheritance
{
[AttributeUsage(
AttributeTargets.All,
//
AllowMultiple=true,
AllowMultiple=false,
Inherited=true
)]
public class SomethingAttribute : Attribute
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public SomethingAttribute(string str)
{
this.name = str;
}
}
[Something("abc")]
class MyClass
{
}
[Something("def")]

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

14
21.01.2007

class Another : MyClass


{
}

//

class Test
{
[STAThread]
static void Main(string[] args)
{
Type type =
Type.GetType("AttribInheritance.Another");
foreach (Attribute attr in
type.GetCustomAttributes(true))
type.GetCustomAttributes(false))
{
SomethingAttribute sa =
attr as SomethingAttribute;
if (null != sa)
{
Console.WriteLine(
"Custom Attribute: {0}",
sa.Name);
}
}
}

Cu AllowMultiple = false, rezultatul este:


Custom Attribute: def

iar cu AllowMultiple = true, rezultatul devine:


Custom Attribute: def
Custom Attribute: abc

Identificatorii atributului
Trebuie sa precizam la ce se aplica un atribut: la o metoda sau la valoarea returnata de
metoda.
Din acest punct de vedere urmatorul cod este ambiguu.
class SomeClass
{

[HRESULT]
public long Foo() { return 0; }
}

In COM, HRESULT reprezinta valoarea returnata de metodele din interfata (mai putin
AddRef si Release, acestea returneaza ULONG).
Putem defini atributul HRESULT astfel:
public class HRESULTAttribute : Attribute
{
public HRESULTAttribute(){}
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

15
21.01.2007

Alte ambiguitati sunt date de urmatoarele situatii:


1.
2.
3.
4.

Metode vs. tip returnat;


Evenimente vs. camp vs. proprietate ;
Delegate vs. tip returnat;
Proprietate vs. accessor vs. valoare returnata din getter vs. valoarea parametrului
unei metode set;

Pentru a fixa lucrurile, putem folosi identificatorul atributului, listat mai jos:

assembly
module
type
method
property
event
field
param
return

Sintaxa este:
identificator:atribut

Atentie la :
Vezi si exemplul de mai jos.
class SomeClass
{
[HRESULT]
public long Foo() { return 0; }
[return: HRESULT]
public long Bar() { return 0; }
}

Alte exemple:
class SomeClass
{
[method: HRESULT]
public long Foo() { return 0; }
[return: HRESULT]
public long Bar() { return 0; }

[property: HRESULT]
public long Goo { get { return 12345; } }

Vom folosi tehnica reflexiei pentru a determina identificatorul atributului, ca in exemplul


urmator:

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

16
21.01.2007

static void Main(string[] args)


{
Type type =
Type.GetType("AttribIdentifiers.SomeClass");
foreach (MethodInfo m in type.GetMethods())
{
foreach (Attribute a in
m.GetCustomAttributes(true))
{
if (a is HRESULTAttribute)
{
Console.WriteLine(
"method: {0}, "
+ "CustomAttributes: {1}",
m.Name, a);
}
}
ICustomAttributeProvider icap =
m.ReturnTypeCustomAttributes;
foreach (Attribute a in
icap.GetCustomAttributes(true))
{
Console.WriteLine(
"method: {0}, "
+ "ReturnTypeCustomAttribs: {1}",
m.Name, a);
}

}
foreach (MemberInfo m in type.GetProperties())
{
foreach (Attribute a in
m.GetCustomAttributes(true))
{
Console.WriteLine(
"property: {0}, "
+ "CustomAttributes: {1}",
m.Name, a);
}
}

Rezultatul este:
method: Foo, CustomAttributes: AttribIdentifiers.HRESULTAttribute
method: Bar, ReturnTypeCustomAttribs:
AttribIdentifiers.HRESULTAttribute
property: Goo, CustomAttributes: AttribIdentifiers.HRESULTAttribute

Atribute predefinite
Cadrul de lucru .NET ofera clase de atribute predefinite, dintre care mai importante sunt:
Predefined .NET
Valid Targets
Attribute
AttributeUsage Class
CLSCompliant

Ioan Asiminoaei

All

Description
Specifies the valid usage of another attribute class.
Indicates whether a program element is compliant
with the Common Language Specification (CLS).

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
Predefined .NET
Valid Targets
Attribute
Conditional

Method

DllImport

Method

MTAThread

Method (Main)

NonSerialized

Field

Obsolete

ParamArray
Serializable
STAThread
StructLayout

ThreadStatic

17
21.01.2007

Description
Indicates that the compiler can ignore any calls to
this method if the associated string is defined.
Specifies the DLL location that contains the
implementation of an external method.
Indicates that the default threading model for an
application is multithreaded apartment (MTA).
Applies to fields of a class flagged as Serializable;
specifies that these fields wont be serialized.

All except
Marks an element obsoletein other words, it
Assembly, Module,
informs the user that the element will be removed
Parameter, and
in future versions of the product.
Return
Allows a single parameter to be implicitly treated
Parameter
as a params (array) parameter.
Class, struct, enum, Specifies that all public and private fields of this
delegate
type can be serialized.
Indicates that the default threading model for an
Method (Main)
application is STA.
Specifies the nature of the data layout of a class or
Class, struct
struct, such as Auto, Explicit, or Sequential.
Implements thread-local storage (TLS)in other
words, the given static field isnt shared across
Field (static)
multiple threads and each thread has its own copy
of the static field.

Atributul Conditional
Ideea: Apelul unei metode poate fi ignorat daca nu este definita o anumita valoare.
Exemplu:
[Conditional("DEBUG")]
public void SomeDebugFunc()
{ Console.WriteLine("SomeDebugFunc"); }

iar cod complet:


using System;
using System.Diagnostics;
namespace CondAttrib
{
class Thing
{
private string name;
public Thing(string name)
{
this.name = name;
SomeDebugFunc();
SomeFunc();
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

18
21.01.2007

public void SomeFunc()


{ Console.WriteLine("SomeFunc"); }
[Conditional("DEBUG")]
public void SomeDebugFunc()
{ Console.WriteLine("SomeDebugFunc"); }
}

public class Class1


{
[STAThread]
static void Main(string[] args)
{
Thing t = new Thing("T1");
}
}

Iesirea este:
SomeDebugFunc
SomeFunc

Pentru a vedea efectele acestui cod trebuiesc modificate proprietatile proiectului.


Compilatorul decide daca functia este compilata sau nu.
Putem combina atributul Conditional cu directivele de preprocesare ca in exemplul:
#if DEBUG
SomeDebugFunc();
#else
SomeFunc();
#endif
Conditional este un atribut cu AllowMultiple = true.

Atributele DllImport si StructLayout


Codul C# poate apela functii in cod nativ ce se gaseste in DLL. Aceasta caracteristica a
runtime-ului se numeste platform invoke.
Exemplu
Vom apela functia Win32 API MessageBoxA ce se gaseste in user32.dll.
Codul este:
public class Test
{
[DllImport ("user32.dll")]
public static extern int MessageBoxA (
int h, string m, string c, int type);
[STAThread]
public static void Main(string[] args)
{
MessageBoxA(0, "Hello World", "nativeDLL", 0);
}
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

19
21.01.2007

Runtime .NET transmite parametrii din codul C# manged catre codul nativ din DLL.
Sintaxa completa pentru DllImport este:
[DllImport("user32", EntryPoint="MessageBoxA",
SetLastError=true,
CharSet=CharSet.Ansi, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern int MessageBoxA (
int h, string m, string c, int type);

Pentru a transmite structuri din codul managed catre codul unmanaged si invers trebuie sa
folosim atributul StructLayout, ce are ca efect construirea structurii exact ca in declaratia ei
initiala (adica secvential). Este obligatoriu acest lucru.
Exemplu pentru apelul functiei GetLocalTime().
[StructLayout(LayoutKind.Sequential)]
public class SystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}

In continuare putem apela functia astfel:


public static void Main(string[] args)
{
SystemTime st = new SystemTime();
GetLocalTime(st);
string s = String.Format("date: {0}-{1}-{2}",
st.wMonth, st.wDay, st.wYear);
string t = String.Format("time: {0}:{1}:{2}",
st.wHour, st.wMinute, st.wSecond);
string u = s + ", " + t;
MessageBoxA(0, u, "Now", 0);
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

20
21.01.2007

Interfete
Interfetele ne dau posibilitatea de a defini o multime de metode si proprietati pe care clasa
selectata le poate implementa.
Din punct de vedere conceptual interfetele sunt contracte intre doua parti de cod separate.
Lucrurile stau in felul urmator:
se defineste o interfata (contine metode abstracte); interfata defineste o anumita
comportare ;
se defineste o clasa ce implementeaza aceasta interfata, se spune ca clasa este derivata
din interfata ;
clientii vor utiliza clasa ce a implementat interfata.
Utilizarea interfetei
In C#, o interfata este un concept de prima clasa, adica o trasatura preconstruita a
limbajului, ce declara un tip referinta ce include numai declaratiile metodelor.
Declararea interfetelor
Interfetele pot contine metode, proprietati, indexeri si evenimente, si nici unul nu este
implementat de interfata insasi. Interfetele sunt definte ca o clasa abstracta.
Exemple
interface IValidate
{
bool Validate();
}
Observatii
Nu trebuie sa declaram ca metodele sunt pur virtuale (=0).
interface IExempluInterface
{
// Exemplu de declarare a unei proprietati
int testProperty { get; }
// Exemplu de declarare a unui eveniment
event testEvent Changed;

// Exemplu de declarare a unui indexer


string this[int index] { get; set; }

Implementarea interfetelor
Fiecare clasa ce implementeaza o interfata trebuie sa defineasca fiecare membru al interfetei.
Sa examinam urmatorul exemplu.

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

21
21.01.2007

using System;

public class Control


{

public Control(string data) { Data = data; }


protected string data;
public string Data
{
get { return data; }
set { data = value; }
}

interface IValidate
{
bool Validate();
}

// implementeaza interfata IValidate


class SSN : Control, IValidate {
const char DELIMITER = '-';
public SSN(string val) : base(val) {}
public bool Validate()
{
Console.WriteLine("[SSN.Validate] : Validating '{0}'",
data);
return (11 == data.Length) && (CorrectFormat());
}
protected bool CorrectFormat()
{
bool correctFormat = true;
for (int i = 0; (correctFormat && i < data.Length); i++)
{
correctFormat =
((IsDelimiterPosition(i)
&& data[i] == DELIMITER)
(IsNumberPosition(i)
&& char.IsNumber(data[i])));
}
return correctFormat;
}
protected bool IsDelimiterPosition(int i)
{ return (i == 3 i == 6); }
protected bool IsNumberPosition(int i)
{ return (i != 3 && i != 6); }
}
// Test pentru tipul construit
// Folosit din linia de comanda

class InterfacesApp
{

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

22
21.01.2007

public static void Main(string[] args)


{
string data = "";
if (0 < args.GetLength(0))
data = args[0];
SSN ssn = new SSN(data);
// ...
// Pentru a valida controlul, scriem urmatorul cod
// o conversie a obiectului...

IValidate val = (IValidate)ssn;


Console.WriteLine("[Main] Apel SSN.Validate");
//
// apelam metoda din interfata
//
bool success = val.Validate();

Console.WriteLine("[Main] The validation of " +


"SSN '{0}' was {1}successful",
ssn.Data,
(true == success ? "" : "NOT "));

Operatorii is si as
Operatorul is test existenta interfata
Pentru a testa un obiect daca implementeaza o anumita interfata folosim operatorul is.
Operatorul is ne permite sa controlam in momentul executiei daca un tip este compatibil cu
alt tip. Sintaxa este:
expresie is ttip
Vezi exemplul ce urmeaza.
using System;
using System.Collections;

public class Control


{
public Control(string data) { Data = data; }
protected string data;
public string Data
{
get { return data; }
set { data = value; }
}
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

23
21.01.2007

interface IValidate
{
}

bool Validate();

// Clasa va implementa interfata IValidate

class SSN : Control , IValidate


{
const char DELIMITER = '-';
public SSN(string val) : base(val) {}
public bool Validate()
{
Console.WriteLine("[SSN.Validate] : Validating '{0}'",
data);
return (11 == data.Length) && (CorrectFormat());
}
protected bool CorrectFormat()
{
bool correctFormat = true;
for (int i = 0; (correctFormat && i < data.Length); i++)
{
correctFormat =
((IsDelimiterPosition(i)
&& data[i] == DELIMITER)
(IsNumberPosition(i)
&& char.IsNumber(data[i])));
}
return correctFormat;
}
protected bool IsDelimiterPosition(int i)
{ return (i == 3 i == 6); }
protected bool IsNumberPosition(int i)
{ return (i != 3 && i != 6); }
}
// Aceasta clasa nu este derivata din IValidate

class Address : Control


{

public Address(string data) : base(data) {}

// Test operator is
class IsOperatorApp
{
public static void Main()
{
// 1. Vom pune in acest ArrayList obiecte instantiate din SSN
// si Address.
// 2. Vom itera acest vector si pentru obiectele instantiate
// din SSN vom apela metoda Validate.

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

24
21.01.2007

ArrayList controls = new ArrayList();


Console.WriteLine("Adauga un obiect SSN in tabloul controls ");
SSN ssn = new SSN("555-55-5555");
controls.Add(ssn);
Console.WriteLine("Adauga un obiect Address in tabloul controls);
Address addr = new Address("Strada Sperantei");
controls.Add(addr);

Console.WriteLine("\nParcurgerea elementelor tabloului ...);


foreach (Control control in controls)
{
if (control is IValidate)
{
Console.WriteLine("\n\t{0} implementeaza IValidate",
control);
Console.Write("\t");
bool b = ((IValidate)control).Validate();
Console.WriteLine("\tValidare {0}",
(b == true ? "succeded" : "failed"));
}
else
{
Console.WriteLine("\t{0} nu implementeaza " +
"IValidate", control);
}
}

Operatorul as
Operatorul as face conversia intre tipurile compatibile si are urmatoarea sintaxa:
obiect = expresie as tip
unde expresie este un tip referinta.
In urma acestei conversii trebuie scris cod de verificare a conversiei, adica daca obiect are o
valoare valida.
Exemplu
using System;
using System.Collections;

public class Control


{
public Control(string data) { Data = data; }
protected string data;
public string Data
{
get { return data; }
set { data = value; }
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

25
21.01.2007

interface IValidate
{

bool Validate();

class SSN : Control , IValidate


{

const char DELIMITER = '-';


public SSN(string val) : base(val) {}
public bool Validate()
{
Console.WriteLine("[SSN.Validate] : Validare '{0}'",
data);
return (11 == data.Length) && (CorrectFormat());
}
protected bool CorrectFormat()
{
bool correctFormat = true;
for (int i = 0; (correctFormat && i < data.Length); i++)
{
correctFormat =
((IsDelimiterPosition(i)
&& data[i] == DELIMITER)
(IsNumberPosition(i)
&& char.IsNumber(data[i])));
}
}

return correctFormat;

protected bool IsDelimiterPosition(int i)


{ return (i == 3 i == 6); }

protected bool IsNumberPosition(int i)


{ return (i != 3 && i != 6); }

class Address : Control


{
public Address(string data) : base(data) {}
}
class AsOperatorApp
{
public static void Main()
{
ArrayList controls = new ArrayList();
Console.WriteLine("Adauga un obiect SSN in vectorul control");
SSN ssn = new SSN("555-55-5555");
controls.Add(ssn);
Console.WriteLine("Adauga un obiect Address in vectorul controls");
Address addr = new Address("Strada Sperantei");
controls.Add(addr);

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

26
21.01.2007

Console.WriteLine("\nIterare elemente din vector...");


IValidate iValidate;
foreach (Control control in controls)
{

iValidate = control as IValidate;

if (null != iValidate)
{
Console.WriteLine("\n\t{0} implementeaza IValidate",
control);
Console.Write("\t");
bool b = iValidate.Validate();
Console.WriteLine("\tValidare {0}",
(b == true ? "succeded" : "failed"));
}
else
{
Console.WriteLine("\n\t{0} nu " +
"implementeaza IValidate", control);
}

}
}

Ascunderea numelor din interfete.


Rezolvarea coliziunilor de nume.
O metoda de apel al unei interfete este:
ClasaEx clx = new ClasaEx();
IInterfataEx iex = (IInterfataEx)clx;
iex.Metoda(); // metoda din interfata
Observatie :
Putem apela si prin sintaxa :
clx.Metoda() ;
Avem posibilitatea de a ascunde membrii publici ai unei interfete, in special a celor ce nu ne
sunt de folos.
Pentru a ascunde un membru al interfetei este nevoie sa stergem modificatorul de acces public
si sa calificam numele membrului cu numele interfetei :
using System;
public interface IDataBound
{
void Bind();
}
public class EditBox : IDataBound
{
// IDataBound implementation
void IDataBound.Bind()
{
Console.WriteLine("Binding to data store...");

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
}

27
21.01.2007

class NameHiding2App
{
public static void Main()
{
Console.WriteLine();
EditBox edit = new EditBox();
Console.WriteLine("Calling EditBox.Bind()...");
// ERROR: This line won't compile because
// the Bind method no longer exists in the
// EditBox class's namespace.

edit.Bind();
Console.WriteLine();

IDataBound bound = (IDataBound)edit;


Console.WriteLine("Calling (IDataBound)" +
"EditBox.Bind()...");
// This is OK because the object was cast to
// IDataBound first.
bound.Bind();
}
}

Cand ascundem un membru, nu putem folosi modificatori de acces.

Coliziuni de nume
Consideram urmatorul exemplu in care avem doua interfete ce contin o metoda cu acelasi
nume, SaveData, dar cu functionalitati diferite in cadrul fiecarei interfete.
using System;

interface ISerializable
{

void SaveData();

interface IDataStore
{
void SaveData();
}

class Test : ISerializable, IDataStore


{

public void SaveData()


{
Console.WriteLine("Test.SaveData called");
}

}
class NameCollisions1App

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi
{

28
21.01.2007

public static void Main()


{
Test test = new Test();
Console.WriteLine("Calling Test.SaveData()");
test.SaveData(); // aici apare ambiguitatea

Urmatorul cod este si mai ambiguu:


using System;

interface ISerializable
{
}

void SaveData();

interface IDataStore
{
void SaveData();
}

class Test : ISerializable, IDataStore


{

public void SaveData()


{
Console.WriteLine("Test.SaveData called");
}

class NameCollisions2App
{
public static void Main()
{
Test test = new Test();
Console.WriteLine("Testing to see if Test " +
"implements ISerializable...");
Console.WriteLine("ISerializable is {0}implemented\n",
test is ISerializable ? "" : "NOT ");
Console.WriteLine("Testing to see if Test " +
"implements IDataStore...");
Console.WriteLine("IDataStore is {0}implemented\n",
test is IDataStore ? "" : "NOT ");
}

Clasa Test apare ca implementind ambele interfete.


Codul se executa (atentie si la versiunea de C#).
Aici trebuie sa fim atenti si la mesajele de averisment emise de compilator :
NameCollisions2.cs(29,21): warning CS0183: The given
expression is always of the provided ('ISerializable') type
NameCollisions2.cs(33,21): warning CS0183: The given
expression is always of the provided ('IDataStore') type

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

29
21.01.2007

Rezolvare problema
using System;

interface ISerializable
{

void SaveData();

interface IDataStore
{
void SaveData();
}

class Test : ISerializable, IDataStore


{

void IDataStore.SaveData()
{
Console.WriteLine("[Test.SaveData] IDataStore " +
"implementation called");
}

void ISerializable.SaveData()
{
}

Console.WriteLine("[Test.SaveData] ISerializable " +


"implementation called");

}
class NameCollisions3App
{
public static void Main()
{
Test test = new Test();
Console.WriteLine("[Main] " +
"Testing to see if Test implements " +
"ISerializable...");
Console.WriteLine("[Main] " +
"ISerializable is {0}implemented",
test is ISerializable ? "" : "NOT ");

((ISerializable)test).SaveData();
Console.WriteLine();
Console.WriteLine("[Main] " +
Testing to see if Test implements " +
"IDataStore...");
Console.WriteLine("[Main] " +
"IDataStore is {0}implemented",
test is IDataStore ? "" : "NOT ");

((IDataStore)test).SaveData();
}
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

30
21.01.2007

Interfete si mostenirea
Cateva scenarii posibile

Clasa de baza ce implementeaza interfata contine o metoda ce are


nume identic cu al unei metode din interfata

Exemplificare
using System;

public class Control


{

public void Serialize()


{
Console.WriteLine("Control.Serialize called");
}

public interface IDataBound


{
// EditBox never implements this, but it still compiles!!!
void Serialize();
}

public class EditBox : Control, IDataBound


{
}

class InterfaceInh1App
{
public static void Main()
{
EditBox edit = new EditBox();
edit.Serialize();
// Care metoda Serialize se executa?
}
}

Codul se compileaza desi nu am scris cod pentru Serialize din IDataBound. Daca codul
executa corect ceea ce am vrut noi este o alta problema.
In fapt se apelaeaza Serialize din clasa de baza.
Folosirea operatorului is nu rezolva problema neimplementarii unei metode din interfata.
Practic is spune ca o anumita clasa suporta o anumita interfata, dar nu ca o si
implementeaza.
Vezi exemplu:
using System;

public class Control


{
public void Serialize()
{ Console.WriteLine("Control.Serialize called");
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

31
21.01.2007

public interface IDataBound


{ void Serialize();
}

public class EditBox : Control, IDataBound


{}

class InterfaceInh2App
{

public static void Main()


{
EditBox edit = new EditBox();

IDataBound bound = edit as IDataBound;


if (bound != null)
{
Console.WriteLine("IDataBound is supported...");
bound.Serialize();
}
else
{
}
}

Console.WriteLine("IDataBound is NOT supported...");

2. Clasa derivata din clasa ce implementeaza o interfata are o metoda cu


acelasi nume ca cea din clasa de baza, nume ce corespunde unei metode din
interfata
.
Sa analizam urmatorul exemplu:
using System;

interface ITest
{
}

void Foo();

// Base implements ITest.

class Base : ITest


{
{
}

public void Foo()


Console.WriteLine("Base.Foo (ITest implementation)");

class MyDerived : Base


{

public new void Foo()


{ Console.WriteLine("MyDerived.Foo");
}

Ioan Asiminoaei

Atribute si interfete
Facultatea de Informatica Iasi Universitatea Al I. Cuza Iasi

32
21.01.2007

public class InterfaceInh3App


{
public static void Main()
{ Console.WriteLine("InterfaceInh3App.Main : " +
"Instantiating a MyDerived class");
MyDerived myDerived = new MyDerived();
Console.WriteLine();
Console.WriteLine("InterfaceInh3App.Main : " +
"Calling MyDerived.Foo - Which method will be " +
"called?");
myDerived.Foo();
Console.WriteLine();
Console.WriteLine("InterfaceInh3App.Main : " +
"Calling MyDerived.Foo - Casting to ITest " +
"interface...");
((ITest)myDerived).Foo();
}
}

new si casting-ul din ultima linie de cod rezolva problema in mod corect.
Se recomanda folosirea cast-ului in utilizarea metodelor din interfete.

Ioan Asiminoaei

S-ar putea să vă placă și