5

Since .NET arrays are covariant, the following works in C#:

var strArray = new string[0];
object[] objArray = strArray;

In F#, given an array, 'T[], what would be the best way to convert it to obj[], without re-creating the array (e.g., Array.map box)? I'm using (box >> unbox), but it feels sloppy.

Daniel
  • 47,404
  • 11
  • 101
  • 179
  • 3
    Interesting. I didn't know this about C#. Isn't this quite a misfeature, compromising static type safety? – wmeyer Sep 07 '11 at 18:58
  • 2
    @wmeyer: http://stackoverflow.com/q/3516619/162396; http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx – Daniel Sep 07 '11 at 18:59
  • 1
    I actually find it very useful for generic classes implementing non-generic interfaces. – Daniel Sep 07 '11 at 19:01

3 Answers3

5

As Brian says, there's nothing wrong with box >> unbox, other that the fact that array covariance is inherently broken (e.g. ([| "test" |] |> box |> unbox<obj[]>).[0] <- obj() will throw an ArrayTypeMismatchException when trying to perform the assignment).

Instead, you would probably be better off treating a string[] as an obj seq, which is perfectly safe (although it still requires boxing and unboxing in F# since F# doesn't support generic co/contra-variance). Unfortunately, you do lose random access if you go this route.

kvb
  • 54,864
  • 2
  • 91
  • 133
  • And unfortunately I need random access. Maybe the bigger question is how to implement a non-generic interface member of type array. My interface is a "paged list of items" (the items plus paging info). So the array element could be any type. Otherwise, I would invent yet another interface to represent the array elements. I guess that's the preferred solution. – Daniel Sep 07 '11 at 19:50
  • 1
    @Daniel - ideally, the BCL would contain a `ReadOnlyArray<'t>` type which was covariant and which `'t[]` derived from. Since it doesn't, you're limited to some less desirable options. Using array covariance is fine as long as you remember never to write to the converted array... – kvb Sep 07 '11 at 19:57
  • 2
    I logged ReadOnlyArray, please vote for it! http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2235515-readonly-array – Brian Sep 07 '11 at 21:30
4
box >> unbox

seems like a good idea; O(1), and does the job, apparently.

Consider also not using this CLR mis-feature. ;)

Brian
  • 117,631
  • 17
  • 236
  • 300
  • 2
    I suppose I could use `Array.map box` but it feels wrong to re-create the array when a perfectly good misfeature is available. ;-) – Daniel Sep 07 '11 at 19:06
3

Brian's solution looks fine to me, but do you really need array covariance?

If you have a function that takes ISomething[] and want to pass it SomeSomething[] then you need it, but if the function only reads values of type ISomething from the array (which is what the covariance allows), then you could use hash-type and write a function that takes #ISomething[]. (Assuming that you can modify the function, of course!)

This way, the function can work on array of any elements that implement ISomething and you don't need array covariance when calling it. Here is an example:

type A() = 
  interface IDisposable with 
    member x.Dispose() = printfn "gone"

// Disposes the first element from an array
let disposeFirst (a:#IDisposable[]) = a.[0].Dispose()

// Call 'disposeFirst' with 'A[]' as an argument
let aa = [| new A(); new A() |]
disposeFirst aa

It seems to me that the main reason for array covariance in .NET is that it was needed at the time when generics did not exist, but it seems that F# can live without this feature just fine.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • How would you implement an interface member returning `obj[]` with a generic type having member of type `'T[]`? The interface doesn't know about `'T`, so I don't see how hash types will help. – Daniel Sep 07 '11 at 19:44
  • @Daniel - I'm not sure I understand the scenario. Can you give some example? A hash type makes the function generic and allows it to take not just `obj[]` but also `foo[]` where `foo` inherits from `obj`, which is the same thing as what array covariance does... – Tomas Petricek Sep 07 '11 at 20:28
  • An interface (which isn't generic) has a member of type `obj[]`. The obstacle is that using a hash type makes the enclosing type generic. The class that implements the interface *is* generic, however (the corresponding member is of type `'T[]`). – Daniel Sep 07 '11 at 20:40
  • 1
    @Daniel - oh, I see - you want to _set the value of a field_. I was (wrongly) assuming that you just want to call some function that takes `obj[]`. In that case, I think Brian's solution is the only reasonable option. – Tomas Petricek Sep 07 '11 at 21:46