I would have used some kind of script language as LUA or Python to define each special behaviors or card.
This way you can define a "generic" engine, and keep the cards particularities away from the main code, into some small and easily editable scripts.
The main advantage of script languages over XML or some other "static" data is that script languages can "create" their own behaviors using the public methods offered by the engine.
Example: suppose your engine offer the methods "setDommage(target,dmg)" and "setVisualEffect(nameOfFxLuaScript)" etc. You can have a cards doing something like this peuso-code
------Card1 -- A simple fireball-----
// Fire ball
setDommage(target, 37);
setVisualEffect("fireBall.lua");
------Card2 -- A multitype ball (outch it hurts!)-----
// Fire ball
setDommage(target, 37);
setVisualEffect("fireBall.lua");
// Ice ball
if( player.protectionAgainstIce == false )
{
setDommage(target, 102);
setVisualEffect("iceBall.lua");
}
// thunderbolt
while( player.Alive )
{
setDommage(target, 1005);
setVisualEffect("thunderBolt.lua");
}
The both cards use the same engine methods, but they don't do the same things. The behaviors can also use data gathered from the engine (like the player list to loop over them) and for, if etc. statements.
The second but not the least advantage, is that it doesn't need to be compiled. You can modify you LUA script and reload it directly without recompiling your game. It's very nice for testing and iterating until you find the good settings.
The third advantage is that, anyone that can learn script language can create new cards/behaviors as long as you provide a list of the public methods shared by the engine (and as long as you don't encode the LUAs). And as anyone knows, the community is incredible when you give them nice tools ;)
Here is a LUA wrapper for C#: http://luaforge.net/projects/luainterface/