Affichage des articles dont le libellé est c#. Afficher tous les articles
Affichage des articles dont le libellé est c#. Afficher tous les articles
3 commentaires dimanche 10 avril 2011

Je me devais de faire part de la découverte suivante.

En C#, l'opérateur as permet de combiner deux opérations communes: tester si un objet est convertible en un type, et effectuer la conversion à proprement parler. Si la conversion échoue, ou si la référence est déjà nulle, as retourne null. Ainsi:


var val = ...;

// les deux bouts suivants sont équivalents:
var covt1 = val as Type;
var covt2 = val is Type ? (Type)val : null;


Un bon exemple serait une redéfinition de la méthode Equals:

class T
{
// ...

public override bool Equals(object obj)
{
var other = obj as T;
if(other == null)
return false; // couvre obj == null et !(obj is T)

// utiliser other pour la comparaison
}
}


En ce sens, c'est un équivalent direct du dynamic_cast de C++, lorsque celui-ci est utilisé sur des pointeurs.

Il y a néanmoins un défaut avec ce opérateur: as retourne null quand la conversion échoue, et null n'est pas une valeur valide pour les types valeur (les struct). On ne peut donc pas utiliser as pour caster un struct.

En pratique, ce n'est pas trop problématique: les structs héritent tous de System.ValueType, sont implicitement sealed, et ne peuvent donc pas faire partie d'une hiérarchie de classe. Les downcastings de ce genre sont rares.

Mais il ne faut pas oublier un détail: un struct hérite également de System.Object, et il possible de faire un downcasting de object à un struct (une procédure appelée "unboxing"). Pour ces cas, donc, j'en étais réduit à l'approche verbeuse (is + cast).

Or, j'ai récemment découvert quelque chose d'intéressant: il est possible d'utiliser as avec un type valeur si celui-ci est nullable:

struct V
{
// ...

public override bool Equals(object obj)
{
var other = obj as V?; // le ? est un raccourci pour Nullable<V>
if(!other.HasValue)
return false;

// utiliser other.Value pour la comparaison
}
}


MSDN n'en parle pas, mais c'est très intéressant. Testé avec C# 4.0, donc ce n'est peut-être pas disponible pour les versions précédentes. Nullable ayant été introduit en .NET 2.0, il est certain que ça ne fonctionnera pas avant.

1 commentaires lundi 28 septembre 2009

Ne vous inquiétez pas, je devrais arriver avec des choses plus intéressantes dans les jours à venir (si j'ai le temps)...

Les templates de C++ permettent d'écrire du code générique, c'est à dire de se faire chier une seule fois pour ensuite pouvoir utiliser le même code partout. Enfin, c'était là leur but premier, et puis un gars (David Vandevoorde, si je me souviens bien) a découvert qu'elles étaient turing complete. Cela signifie qu'on peut écrire, avec des templates, des programmes qui s'exécuteront à la compilation.

L'exemple le plus répandu est une factorielle en O(1):


#include <iostream>

template<int N>
struct Factorial
{
static const int Value = N * Factorial<N - 1>::Value;
};

template<>
struct Factorial<0>
{
static const int Value = 1;
};

int main()
{
std::cout << Factorial<6>::Value; // affiche 720
}
La valeur de la factorielle sera évaluée à la compilation, et sera équivalente à une constante.

On peut également créer des assertions statiques (des tests qui font planter la compilation s'ils ne passent pas) avec des templates. À noter qu'une telle pratique sera rendue obsolète en C++0x, qui inclura un support standard pour de telles assertions.

En Java, pour éviter les abus de downcasts de Object à quelque chose, on a introduit dans la version 1.5 les Generics. C# a eu tôt fait de copier le concept dans sa version 2.0. Mis à part une syntaxe légèrement différente pour les contraintes, la principale nuance entre C# et Java est qu'en Java, les Generics sont remplacés par des Object avec des casts, alors qu'en C#, ils sont remplacés par le type utilisé. Ces deux approches se valent, bien que je préfère celle de C#: elle permet, entre autres, d'effectuer de la réflexion sur les types génériques.

Pour celui qui connaît peu les templates de C++, on serait porté à croire que les Generics sont très semblables, mais il y a énormément de différences entre les deux. La plus importante est que les templates sont instanciés à la compilation, et le compilateur peut donc évaluer immédiatement si elles sont correctement utilisées (et si ce n'est pas le cas, générer un message d'erreur incompréhensible). En Java et en C#, les generics sont instanciés à l'exécution, et le compilateur ne peut donc pas s'assurer qu'elles sont bien utilisées. Le programmeur doit donc insérer des "contraintes", sinon, le type générique sera considéré comme un Object, ce qui tue un peu le principe. De telles contraintes sont inutiles en C++, car les templates répondent au principe de SFINAE.

Pourquoi une telle discussion? J'ai trouvé un vieil article sur MSDN décrivant les différences entre les templates de C++ et les Generics de C#:
http://msdn.microsoft.com/en-us/library/c6cyy67b(loband).aspx
En passant, pour ceux qui ne le savent pas, le "(loband)" dans l'URL, c'est pour activer le "low bandwidth mode" sur MSDN, vous permettant de sauver de la bande passante.

Voilà.

1 commentaires mercredi 22 juillet 2009

Java était (et est toujours, d'ailleurs) un langage "purement orienté objet". Quelle décision étrange. Non seulement Java est, selon moi, moins orienté objet que C++ (presque pas de contrôle sur la fin de vie d'un objet, notamment), mais ça voulait également dire qu'il ne pouvait faire rien d'autre que de la POO. Pourtant, je me dis que du procédural, parfois, c'est utile.

C# répète à peu près les mêmes erreurs que Java pour la POO, mais compense en s'ouvrant à d'autres paradigmes. Dans sa version 3.0, C# a donc mis un pied (ou plutôt le gros orteil) dans le monde de la programmation fonctionnelle avec des lambda expressions et des expression trees (bon, on pouvait déjà faire ça depuis 1998 en C++ avec des templates, mais bel effort quand même). Et depuis sa toute première version, C# supporte un hybride entre méthodes et fonctions libres sous la forme de délégués (un pointeur déguisé sur une fonction, en fait). Bon, d'accord, je pourrais également faire le parallèle avec C++, mais pourquoi taper sur quelqu'un qui est à terre?

Je sais pas pour vous, mais moi, écrire une classe pour ne l'utiliser qu'une seule fois, comme foncteur sans état en plus, je trouve ça lourd. C'est d'ailleurs pourquoi, en C++, je préfère les fonctions aux foncteurs lorsque c'est possible: c'est beaucoup moins verbeux (mais bon, une fonction n'a pas d'état, alors il faut parfois sortir les foncteurs). Alors bon, quand je vois, dans le .NET Framework, une méthode qui attend un IEqualityComparer<T>, je sacre. Pourquoi devrais-je implémenter une classe pour une fonctionnalité qui n'existera que là? Ne serait-il pas plus simple de passer un délégué à la place? Martin Fowler n'a-t-il pas dit que la généricité préventive était néfaste?

J'ai donc retroussé mes manches, et voici ce à quoi je suis arrivé:


public static class EqualityComparer
{
private class DelegateComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _equalityFunction; // prend deux T en paramètre et retourne un bool
private readonly Func<T, int> _hashCodeFunction; // prend un T en paramètre et retourne un int

public DelegateComparer(Func<T, T, bool> equalityFunction, Func<T, int> hashCodeFunction)
{
if (equalityFunction == null || hashCodeFunction == null)
throw new ArgumentNullException();
_equalityFunction = equalityFunction;
_hashCodeFunction = hashCodeFunction;
}

public bool Equals(T x, T y)
{
return _equalityFunction(x, y);
}

public int GetHashCode(T obj)
{
return _hashCodeFunction(obj);
}
}

public static IEqualityComparer<T> Make<T>(Func<T, T, bool> EqualityFunction, Func<T, int> HashCodeFunction)
{
return new DelegateComparer<T>(EqualityFunction, HashCodeFunction);
}
}

Bon, quel intérêt? Eh bien, admettons (cet exemple utilise des lambdas, mais vous pourriez très bien passer des méthodes, tant qu'elles ont la bonne signature):

var absComparer = EqualityComparer.Make<int>(
(p1, p2) => Math.Abs(p1) == Math.Abs(p2),
p => Math.Abs(p).GetHashCode());

Et voilà, un IEqualityComparer<T> en une ligne (étalée sur plusieurs lignes pour plus de clarté)! C'est tu pas wonderful?

Dites-moi ce que vous en pensez. Comme toujours, ce n'est pas final.

0 commentaires lundi 16 mars 2009

J'ai récemment voulu peupler les données d'un DropDownList ASP.NET avec les éléments contenus dans une énumération. Avec un peu de recherches, j'ai découvert la classe utilitaire Enum permettant d'obtenir des informations intéressantes sur une énumération. J'ai donc codé une méthode pour retourner sous la forme d'un Dictionnary les éléments d'une énumération donnée:


using System;
using System.Collections.Generic;

namespace Util
{
public static class EnumUtils
{
/// <summary>
/// Obtient les éléments composant une énumération
/// </summary>
/// <typeparam name="TEnum">Le type de l'énumération</typeparam>
/// <exception cref="ArgumentException">Lancée si TEnum n'est pas une énumération</exception>
/// <returns>Un dictionnaire avec comme clé la valeur entière d'un élément et comme valeur, son nom</returns>
public static IDictionary<int, string> GetEnumElements<TEnum>()
{

var type = typeof(TEnum);
if (!type.IsEnum)
throw new ArgumentException("Type must be an enum type");

var names = Enum.GetNames(type);
var values = Enum.GetValues(type);

var elements = new Dictionary<int, string>();
for (int i = 0; i < names.Length; ++i)
{
elements.Add((int)values.GetValue(i), names[i]);
}
return elements;
}
}
}

La présence du mot clé var force l'utilisation d'un compilateur respectant la norme 3.0 du langage, mais il suffit de retirer ceci pour permettra la compatibilité avec la version 2.0.